Forráskód Böngészése

- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
- changed the shortcut key for Transform Tool from 'T' to 'ALT+T'
- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
- added Double Validator for the Offset value so only float numbers can be entered.
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab

Marius Stanciu 7 éve
szülő
commit
8f000c0a18
11 módosított fájl, 347 hozzáadás és 112 törlés
  1. 62 9
      FlatCAMApp.py
  2. 20 4
      FlatCAMEditor.py
  3. 91 12
      FlatCAMGUI.py
  4. 78 57
      FlatCAMObj.py
  5. 3 0
      FlatCAMTool.py
  6. 7 4
      GUIElements.py
  7. 2 1
      ObjectUI.py
  8. 7 0
      README.md
  9. 76 25
      camlib.py
  10. 1 0
      flatcamTools/ToolNonCopperClear.py
  11. BIN
      share/letter_t_32.png

+ 62 - 9
FlatCAMApp.py

@@ -1714,16 +1714,14 @@ class App(QtCore.QObject):
         try:
             if error:
                 self.shell.append_error(msg + "\n")
+            elif warning:
+                self.shell.append_warning(msg + "\n")
+            elif success:
+                self.shell.append_success(msg + "\n")
+            elif selected:
+                self.shell.append_selected(msg + "\n")
             else:
-                if warning:
-                    self.shell.append_warning(msg + "\n")
-                else:
-                    if success:
-                        self.shell.append_success(msg + "\n")
-                        if success:
-                            self.shell.append_selected(msg + "\n")
-                        else:
-                            self.shell.append_output(msg + "\n")
+                self.shell.append_output(msg + "\n")
         except AttributeError:
             log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg))
 
@@ -2298,6 +2296,7 @@ class App(QtCore.QObject):
             self.collection.set_all_inactive()
             self.collection.set_active(obj.options["name"])
 
+        # here it is done the object plotting
         def worker_task(obj):
             with self.proc_container.new("Plotting"):
                 if isinstance(obj, FlatCAMCNCjob):
@@ -2740,6 +2739,60 @@ class App(QtCore.QObject):
 
         self.inform.emit("[success] A Geometry object was converted to SingleGeo type.")
 
+    def on_skey_tool_add(self):
+        ## Current application units in Upper Case
+        self.units = self.general_options_form.general_app_group.units_radio.get_value().upper()
+
+        # work only if the notebook tab on focus is the Selected_Tab and only if the object is Geometry
+        if self.ui.notebook.currentWidget().objectName() == 'selected_tab':
+            if str(type(self.collection.get_active())) == "<class 'FlatCAMObj.FlatCAMGeometry'>":
+                tool_add_popup = FCInputDialog(title="New Tool ...",
+                                               text='Enter a Tool Diameter:',
+                                               min=0.0000, max=99.9999, decimals=4)
+                tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
+
+                val, ok = tool_add_popup.get_value()
+                if ok:
+                    self.collection.get_active().on_tool_add(dia=float(val))
+                    self.inform.emit(
+                        "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units)))
+                else:
+                    self.inform.emit(
+                        "[WARNING_NOTCL] Adding Tool cancelled ...")
+
+        # work only if the notebook tab on focus is the Tools_Tab
+        if self.ui.notebook.currentWidget().objectName() == 'tool_tab':
+            # and only if the tool is NCC Tool
+            if self.ui.tool_scroll_area.widget().objectName() == self.ncclear_tool.toolName:
+                tool_add_popup = FCInputDialog(title="New Tool ...",
+                                               text='Enter a Tool Diameter:',
+                                               min=0.0000, max=99.9999, decimals=4)
+                tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
+
+                val, ok = tool_add_popup.get_value()
+                if ok:
+                    self.ncclear_tool.on_tool_add(dia=float(val))
+                    self.inform.emit(
+                        "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units)))
+                else:
+                    self.inform.emit(
+                        "[WARNING_NOTCL] Adding Tool cancelled ...")
+            # and only if the tool is Paint Area Tool
+            if self.ui.tool_scroll_area.widget().objectName() == self.paint_tool.toolName:
+                tool_add_popup = FCInputDialog(title="New Tool ...",
+                                               text='Enter a Tool Diameter:',
+                                               min=0.0000, max=99.9999, decimals=4)
+                tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
+
+                val, ok = tool_add_popup.get_value()
+                if ok:
+                    self.paint_tool.on_tool_add(dia=float(val))
+                    self.inform.emit(
+                        "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units)))
+                else:
+                    self.inform.emit(
+                        "[WARNING_NOTCL] Adding Tool cancelled ...")
+
     def on_options_dict_change(self, field):
         self.options_write_form_field(field)
 

+ 20 - 4
FlatCAMEditor.py

@@ -3255,7 +3255,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         grid1.addWidget(addtool_entry_lbl, 0, 0)
 
         hlay = QtWidgets.QHBoxLayout()
-        self.addtool_entry = LengthEntry()
+        self.addtool_entry = FCEntry()
+        self.addtool_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
         hlay.addWidget(self.addtool_entry)
 
         self.addtool_btn = QtWidgets.QPushButton('Add Tool')
@@ -3663,7 +3664,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         if self.units == "IN":
             self.addtool_entry.set_value(0.039)
         else:
-            self.addtool_entry.set_value(1)
+            self.addtool_entry.set_value(1.00)
 
         sort_temp = []
 
@@ -3847,9 +3848,21 @@ class FlatCAMExcEditor(QtCore.QObject):
         # we reactivate the signals after the after the tool adding as we don't need to see the tool been populated
         self.tools_table_exc.itemChanged.connect(self.on_tool_edit)
 
-    def on_tool_add(self):
+    def on_tool_add(self, tooldia=None):
         self.is_modified = True
-        tool_dia = float(self.addtool_entry.get_value())
+        if tooldia:
+            tool_dia = tooldia
+        else:
+            try:
+                tool_dia = float(self.addtool_entry.get_value())
+            except ValueError:
+                # try to convert comma to decimal point. if it's still not working error message and return
+                try:
+                    tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
+                except ValueError:
+                    self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
+                                         "use a number.")
+                    return
 
         if tool_dia not in self.olddia_newdia:
             storage_elem = FlatCAMGeoEditor.make_storage()
@@ -4156,6 +4169,9 @@ class FlatCAMExcEditor(QtCore.QObject):
 
         self.replot()
 
+        # add a first tool in the Tool Table
+        self.on_tool_add(tooldia=1.00)
+
     def update_fcexcellon(self, exc_obj):
         """
         Create a new Excellon object that contain the edited content of the source Excellon object

+ 91 - 12
FlatCAMGUI.py

@@ -563,6 +563,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         ### Project ###
         self.project_tab = QtWidgets.QWidget()
+        self.project_tab.setObjectName("project_tab")
         # project_tab.setMinimumWidth(250)  # Hack
         self.project_tab_layout = QtWidgets.QVBoxLayout(self.project_tab)
         self.project_tab_layout.setContentsMargins(2, 2, 2, 2)
@@ -570,6 +571,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         ### Selected ###
         self.selected_tab = QtWidgets.QWidget()
+        self.selected_tab.setObjectName("selected_tab")
         self.selected_tab_layout = QtWidgets.QVBoxLayout(self.selected_tab)
         self.selected_tab_layout.setContentsMargins(2, 2, 2, 2)
         self.selected_scroll_area = VerticalScrollArea()
@@ -578,6 +580,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 
         ### Tool ###
         self.tool_tab = QtWidgets.QWidget()
+        self.tool_tab.setObjectName("tool_tab")
         self.tool_tab_layout = QtWidgets.QVBoxLayout(self.tool_tab)
         self.tool_tab_layout.setContentsMargins(2, 2, 2, 2)
         self.notebook.addTab(self.tool_tab, "Tool")
@@ -826,6 +829,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 			<td height="20"><strong>S</strong></td>
 			<td>&nbsp;Shell Toggle</td>
 		</tr>
+        <tr height="20">
+			<td height="20"><strong>T</strong></td>
+			<td>&nbsp;Add a Tool (when in Geometry Selected Tab)</td>
+		</tr>
 		<tr height="20">
 			<td height="20"><strong>V</strong></td>
 			<td>&nbsp;Zoom Fit</td>
@@ -948,7 +955,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 		</tr>
 		<tr height="20">
 			<td height="20"><strong>ALT+R</strong></td>
-			<td>&nbsp;Transformation Tool</td>
+			<td>&nbsp;Transformations Tool</td>
 		</tr>
 		<tr height="20">
 			<td height="20"><strong>ALT+U</strong></td>
@@ -1131,6 +1138,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
 			<td height="20"><strong>R</strong></td>
 			<td>&nbsp;Resize Drill(s)</td>
 		</tr>
+        <tr height="20">
+			<td height="20"><strong>T</strong></td>
+			<td>&nbsp;Add a new Tool</td>
+		</tr>
 		<tr height="20">
 			<td height="20">&nbsp;</td>
 			<td>&nbsp;</td>
@@ -1755,9 +1766,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 if key == QtCore.Qt.Key_S:
                     self.app.on_toggle_shell()
 
-                # Transform Tool
+                # Add a Tool from shortcut
                 if key == QtCore.Qt.Key_T:
-                    self.app.transform_tool.run()
+                    self.app.on_skey_tool_add()
 
                 # Zoom Fit
                 if key == QtCore.Qt.Key_V:
@@ -1801,6 +1812,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
             elif modifiers == QtCore.Qt.AltModifier:
                 pass
             else:
+                # toggle display of Notebook area
+                if key == QtCore.Qt.Key_QuoteLeft or key == '`':
+                    self.app.on_toggle_notebook()
+
                 # Finish the current action. Use with tools that do not
                 # complete automatically, like a polygon or path.
                 if key == QtCore.Qt.Key_Enter or key == 'Enter':
@@ -1840,14 +1855,25 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     self.app.geo_editor.active_tool.set_origin(
                         self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y))
 
-                if key == QtCore.Qt.Key_1 or key== '1':
-                    self.app.on_zoom_fit(None)
+                if key == QtCore.Qt.Key_Minus or key == '-':
+                    self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'],
+                                             [self.app.geo_editor.snap_x, self.app.geo_editor.snap_y])
 
+                if key == QtCore.Qt.Key_Equal or key == '=':
+                    self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'],
+                                             [self.app.geo_editor.snap_x, self.app.geo_editor.snap_y])
+
+                # Switch to Project Tab
+                if key == QtCore.Qt.Key_1 or key == '1':
+                    self.app.on_select_tab('project')
+
+                # Switch to Selected Tab
                 if key == QtCore.Qt.Key_2 or key == '2':
-                    self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
+                    self.app.on_select_tab('selected')
 
+                # Switch to Tool Tab
                 if key == QtCore.Qt.Key_3 or key == '3':
-                    self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
+                    self.app.on_select_tab('tool')
 
                 # Arc Tool
                 if key == QtCore.Qt.Key_A or key == 'A':
@@ -1954,6 +1980,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
                         messagebox.exec_()
 
+                if key == QtCore.Qt.Key_V or key == 'V':
+                    self.app.on_zoom_fit(None)
+
                 # Cut Action Tool
                 if key == QtCore.Qt.Key_X or key == 'X':
                     if self.app.geo_editor.get_selected() is not None:
@@ -1975,7 +2004,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                 # Propagate to tool
                 response = None
                 if self.app.geo_editor.active_tool is not None:
-                    response = self.app.geo_editor.active_tool.on_key(event.key)
+                    response = self.app.geo_editor.active_tool.on_key(event)
                 if response is not None:
                     self.app.inform.emit(response)
 
@@ -2024,17 +2053,41 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                         self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.")
                     return
 
+                if key == QtCore.Qt.Key_Minus or key == '-':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'],
+                                             [self.app.exc_editor.snap_x, self.app.exc_editor.snap_y])
+                    return
+
+                if key == QtCore.Qt.Key_Equal or key == '=':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'],
+                                             [self.app.exc_editor.snap_x, self.app.exc_editor.snap_y])
+                    return
+
+                # toggle display of Notebook area
+                if key == QtCore.Qt.Key_QuoteLeft or key == '`':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    self.app.on_toggle_notebook()
+                    return
+
+                # Switch to Project Tab
                 if key == QtCore.Qt.Key_1 or key == '1':
                     self.app.exc_editor.launched_from_shortcuts = True
-                    self.app.on_zoom_fit(None)
+                    self.app.on_select_tab('project')
+                    return
 
+                # Switch to Selected Tab
                 if key == QtCore.Qt.Key_2 or key == '2':
                     self.app.exc_editor.launched_from_shortcuts = True
-                    self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
+                    self.app.on_select_tab('selected')
+                    return
 
+                # Switch to Tool Tab
                 if key == QtCore.Qt.Key_3 or key == '3':
                     self.app.exc_editor.launched_from_shortcuts = True
-                    self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
+                    self.app.on_select_tab('tool')
+                    return
 
                 # Add Array of Drill Hole Tool
                 if key == QtCore.Qt.Key_A or key == 'A':
@@ -2100,10 +2153,36 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
                     self.app.exc_editor.select_tool('resize')
                     return
 
+                # Add Tool
+                if key == QtCore.Qt.Key_T or key == 'T':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    ## Current application units in Upper Case
+                    self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
+                    tool_add_popup = FCInputDialog(title="New Tool ...",
+                                                   text='Enter a Tool Diameter:',
+                                                   min=0.0000, max=99.9999, decimals=4)
+                    tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
+
+                    val, ok = tool_add_popup.get_value()
+                    if ok:
+                        self.app.exc_editor.on_tool_add(tooldia=val)
+                        self.app.inform.emit(
+                            "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units)))
+                    else:
+                        self.app.inform.emit(
+                            "[WARNING_NOTCL] Adding Tool cancelled ...")
+                    return
+
+                # Zoom Fit
+                if key == QtCore.Qt.Key_V or key == 'V':
+                    self.app.exc_editor.launched_from_shortcuts = True
+                    self.app.on_zoom_fit(None)
+                    return
+
                 # Propagate to tool
                 response = None
                 if self.app.exc_editor.active_tool is not None:
-                    response = self.app.exc_editor.active_tool.on_key(event.key)
+                    response = self.app.exc_editor.active_tool.on_key(event)
                 if response is not None:
                     self.app.inform.emit(response)
 

+ 78 - 57
FlatCAMObj.py

@@ -480,7 +480,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
         def geo_init(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
-            bounding_box = self.solid_geometry.envelope.buffer(self.options["noncoppermargin"])
+            bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
             if not self.options["noncopperrounded"]:
                 bounding_box = bounding_box.envelope
             non_copper = bounding_box.difference(self.solid_geometry)
@@ -497,7 +497,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         def geo_init(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
             # Bounding box with rounded corners
-            bounding_box = self.solid_geometry.envelope.buffer(self.options["bboxmargin"])
+            bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"]))
             if not self.options["bboxrounded"]:  # Remove rounded corners
                 bounding_box = bounding_box.envelope
             geo_obj.solid_geometry = bounding_box
@@ -557,7 +557,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
         def follow_init(follow_obj, app):
             # Propagate options
-            follow_obj.options["cnctooldia"] = self.options["isotooldia"]
+            follow_obj.options["cnctooldia"] = float(self.options["isotooldia"])
             follow_obj.solid_geometry = self.solid_geometry
 
         # TODO: Do something if this is None. Offer changing name?
@@ -579,11 +579,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         """
         if dia is None:
-            dia = self.options["isotooldia"]
+            dia = float(self.options["isotooldia"])
         if passes is None:
             passes = int(self.options["isopasses"])
         if overlap is None:
-            overlap = self.options["isooverlap"]
+            overlap = float(self.options["isooverlap"])
         if combine is None:
             combine = self.options["combine_passes"]
         else:
@@ -638,7 +638,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             # TODO: This is ugly. Create way to pass data into init function.
             def iso_init(geo_obj, app_obj):
                 # Propagate options
-                geo_obj.options["cnctooldia"] = self.options["isotooldia"]
+                geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
                 geo_obj.solid_geometry = []
                 for i in range(passes):
                     iso_offset = (((2 * i + 1) / 2.0) * dia) - (i * overlap * dia)
@@ -693,7 +693,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                 # TODO: This is ugly. Create way to pass data into init function.
                 def iso_init(geo_obj, app_obj):
                     # Propagate options
-                    geo_obj.options["cnctooldia"] = self.options["isotooldia"]
+                    geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
 
                     # if milling type is climb then the move is counter-clockwise around features
                     if milling_type == 'cl':
@@ -753,8 +753,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
         factor = Gerber.convert_units(self, units)
 
-        self.options['isotooldia'] *= factor
-        self.options['bboxmargin'] *= factor
+        self.options['isotooldia'] = float(self.options['isotooldia']) * factor
+        self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor
 
     def plot(self, **kwargs):
         """
@@ -1458,7 +1458,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             outname = self.options["name"] + "_mill"
 
         if tooldia is None:
-            tooldia = self.options["tooldia"]
+            tooldia = float(self.options["tooldia"])
 
         # Sort tools by diameter. items() -> [('name', diameter), ...]
         # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
@@ -1545,7 +1545,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             outname = self.options["name"] + "_mill"
 
         if tooldia is None:
-            tooldia = self.options["slot_tooldia"]
+            tooldia = float(self.options["slot_tooldia"])
 
         # Sort tools by diameter. items() -> [('name', diameter), ...]
         # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
@@ -1693,13 +1693,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             job_obj.options['ppname_e'] = pp_excellon_name
 
             app_obj.progress.emit(20)
-            job_obj.z_cut = self.options["drillz"]
-            job_obj.z_move = self.options["travelz"]
-            job_obj.feedrate = self.options["feedrate"]
-            job_obj.feedrate_rapid = self.options["feedrate_rapid"]
-            job_obj.spindlespeed = self.options["spindlespeed"]
+            job_obj.z_cut = float(self.options["drillz"])
+            job_obj.z_move = float(self.options["travelz"])
+            job_obj.feedrate = float(self.options["feedrate"])
+            job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
+            job_obj.spindlespeed = float(self.options["spindlespeed"])
             job_obj.dwell = self.options["dwell"]
-            job_obj.dwelltime = self.options["dwelltime"]
+            job_obj.dwelltime = float(self.options["dwelltime"])
             job_obj.pp_excellon_name = pp_excellon_name
 
             job_obj.toolchange_xy_type = "excellon"
@@ -1738,12 +1738,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
 
             tools_csv = ','.join(tools)
             ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv,
-                                                             drillz=self.options['drillz'],
-                                                             toolchange=self.options["toolchange"],
+                                                             drillz=float(self.options['drillz']),
+                                                             toolchange=float(self.options["toolchange"]),
                                                              toolchangexy=self.app.defaults["excellon_toolchangexy"],
-                                                             toolchangez=self.options["toolchangez"],
-                                                             startz=self.options["startz"],
-                                                             endz=self.options["endz"],
+                                                             toolchangez=float(self.options["toolchangez"]),
+                                                             startz=float(self.options["startz"]) if
+                                                             self.options["startz"] else None,
+                                                             endz=float(self.options["endz"]),
                                                              excellon_optimization_type=self.app.defaults[
                                                                  "excellon_optimization_type"])
             if ret_val == 'fail':
@@ -1783,11 +1784,11 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     def convert_units(self, units):
         factor = Excellon.convert_units(self, units)
 
-        self.options['drillz'] *= factor
-        self.options['travelz'] *= factor
-        self.options['feedrate'] *= factor
-        self.options['feedrate_rapid'] *= factor
-        self.options['toolchangez'] *= factor
+        self.options['drillz'] = float(self.options['drillz']) * factor
+        self.options['travelz'] = float(self.options['travelz']) * factor
+        self.options['feedrate'] = float(self.options['feedrate']) * factor
+        self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
+        self.options['toolchangez'] = float(self.options['toolchangez']) * factor
 
         if self.app.defaults["excellon_toolchangexy"] == '':
             self.options['toolchangexy'] = "0.0, 0.0"
@@ -1802,8 +1803,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
 
         if self.options['startz'] is not None:
-            self.options['startz'] *= factor
-        self.options['endz'] *= factor
+            self.options['startz'] = float(self.options['startz']) * factor
+        self.options['endz'] = float(self.options['endz']) * factor
 
     def plot(self):
 
@@ -2271,7 +2272,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         if not self.tools:
             self.tools.update({
                 self.tooluid: {
-                    'tooldia': self.options["cnctooldia"],
+                    'tooldia': float(self.options["cnctooldia"]),
                     'offset': 'Path',
                     'offset_value': 0.0,
                     'type': 'Rough',
@@ -2816,8 +2817,28 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.cutz_entry.setDisabled(False)
 
     def update_cutz(self):
-        vdia = float(self.ui.tipdia_entry.get_value())
-        half_vangle = float(self.ui.tipangle_entry.get_value()) / 2
+        try:
+            vdia = float(self.ui.tipdia_entry.get_value())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                vdia = float(self.ui.tipdia_entry.get_value().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
+                                     "use a number.")
+                return
+
+        try:
+            half_vangle = float(self.ui.tipangle_entry.get_value()) / 2
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                half_vangle = float(self.ui.tipangle_entry.get_value().replace(',', '.')) / 2
+            except ValueError:
+                self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
+                                     "use a number.")
+                return
+
 
         row = self.ui.geo_tools_table.currentRow()
         tool_uid = int(self.ui.geo_tools_table.item(row, 5).text())
@@ -3615,36 +3636,36 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         :return: None
         """
 
-        tooldia = tooldia if tooldia else self.options["cnctooldia"]
-        outname = outname if outname is not None else self.options["name"]
+        tooldia = tooldia if tooldia else float(self.options["cnctooldia"])
+        outname = outname if outname is not None else float(self.options["name"])
 
-        z_cut = z_cut if z_cut is not None else self.options["cutz"]
-        z_move = z_move if z_move is not None else self.options["travelz"]
+        z_cut = z_cut if z_cut is not None else float(self.options["cutz"])
+        z_move = z_move if z_move is not None else float(self.options["travelz"])
 
-        feedrate = feedrate if feedrate is not None else self.options["feedrate"]
-        feedrate_z = feedrate_z if feedrate_z is not None else self.options["feedrate_z"]
-        feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else self.options["feedrate_rapid"]
+        feedrate = feedrate if feedrate is not None else float(self.options["feedrate"])
+        feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"])
+        feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
 
         multidepth = multidepth if multidepth is not None else self.options["multidepth"]
-        depthperpass = depthperpass if depthperpass is not None else self.options["depthperpass"]
+        depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
 
         segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
         segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
 
-        extracut = extracut if extracut is not None else self.options["extracut"]
-        startz = startz if startz is not None else self.options["startz"]
-        endz = endz if endz is not None else self.options["endz"]
+        extracut = extracut if extracut is not None else float(self.options["extracut"])
+        startz = startz if startz is not None else float(self.options["startz"])
+        endz = endz if endz is not None else float(self.options["endz"])
 
-        toolchangez = toolchangez if toolchangez else self.options["toolchangez"]
+        toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
         toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
         toolchange = toolchange if toolchange else self.options["toolchange"]
 
         offset = offset if offset else 0.0
 
         # int or None.
-        spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed']
+        spindlespeed = spindlespeed if spindlespeed else int(self.options['spindlespeed'])
         dwell = dwell if dwell else self.options["dwell"]
-        dwelltime = dwelltime if dwelltime else self.options["dwelltime"]
+        dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"])
 
         ppname_g = ppname_g if ppname_g else self.options["ppname_g"]
 
@@ -3827,19 +3848,19 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         factor = Geometry.convert_units(self, units)
 
-        self.options['cutz'] *= factor
-        self.options['depthperpass'] *= factor
-        self.options['travelz'] *= factor
-        self.options['feedrate'] *= factor
-        self.options['feedrate_z'] *= factor
-        self.options['feedrate_rapid'] *= factor
-        self.options['endz'] *= factor
+        self.options['cutz'] = float(self.options['cutz']) * factor
+        self.options['depthperpass'] = float(self.options['depthperpass']) * factor
+        self.options['travelz'] = float(self.options['travelz']) * factor
+        self.options['feedrate'] = float(self.options['feedrate']) * factor
+        self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor
+        self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
+        self.options['endz'] = float(self.options['endz']) * factor
         # self.options['cnctooldia'] *= factor
         # self.options['painttooldia'] *= factor
         # self.options['paintmargin'] *= factor
         # self.options['paintoverlap'] *= factor
 
-        self.options["toolchangez"] *= factor
+        self.options["toolchangez"] = float(self.options["toolchangez"]) * factor
 
         if self.app.defaults["geometry_toolchangexy"] == '':
             self.options['toolchangexy'] = "0.0, 0.0"
@@ -3854,7 +3875,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
 
         if self.options['startz'] is not None:
-            self.options['startz'] *= factor
+            self.options['startz'] = float(self.options['startz']) * factor
 
         param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
                       'endz', 'toolchangez']
@@ -4582,7 +4603,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
         try:
             if self.multitool is False: # single tool usage
-                self.plot2(tooldia=self.options["tooldia"], obj=self, visible=visible, kind=kind)
+                self.plot2(tooldia=float(self.options["tooldia"]), obj=self, visible=visible, kind=kind)
             else:
                 # multiple tools usage
                 for tooluid_key in self.cnc_tools:
@@ -4597,7 +4618,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
     def convert_units(self, units):
         factor = CNCjob.convert_units(self, units)
         FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_units()")
-        self.options["tooldia"] *= factor
+        self.options["tooldia"] = float(self.options["tooldia"]) * factor
 
         param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
                       'endz', 'toolchangez']

+ 3 - 0
FlatCAMTool.py

@@ -83,6 +83,9 @@ class FlatCAMTool(QtWidgets.QWidget):
         # Put ourself in the GUI
         self.app.ui.tool_scroll_area.setWidget(self)
 
+        # Set the tool name as the widget object name
+        self.app.ui.tool_scroll_area.widget().setObjectName(self.toolName)
+
         # Switch notebook to tool page
         self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
 

+ 7 - 4
GUIElements.py

@@ -233,7 +233,6 @@ class FloatEntry(QtWidgets.QLineEdit):
         else:
             self.setText("")
 
-
     def sizeHint(self):
         default_hint_size = super(FloatEntry, self).sizeHint()
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
@@ -351,7 +350,7 @@ class FCEntry2(FCEntry):
         self.readyToEdit = True
 
     def set_value(self, val):
-        self.setText('%.5f' % float(val))
+        self.setText('%.4f' % float(val))
 
 
 class EvalEntry(QtWidgets.QLineEdit):
@@ -474,6 +473,7 @@ class FCTextAreaRich(QtWidgets.QTextEdit):
         default_hint_size = super(FCTextAreaRich, self).sizeHint()
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
 
+
 class FCComboBox(QtWidgets.QComboBox):
     def __init__(self, parent=None):
         super(FCComboBox, self).__init__(parent)
@@ -494,6 +494,10 @@ class FCInputDialog(QtWidgets.QInputDialog):
         super(FCInputDialog, self).__init__(parent)
         self.allow_empty = ok
         self.empty_val = val
+
+        self.val = 0.0
+        self.ok = ''
+
         if title is None:
             self.title = 'title'
         else:
@@ -515,9 +519,8 @@ class FCInputDialog(QtWidgets.QInputDialog):
         else:
             self.decimals = decimals
 
-
     def get_value(self):
-        self.val,self.ok = self.getDouble(self, self.title, self.text, min=self.min,
+        self.val, self.ok = self.getDouble(self, self.title, self.text, min=self.min,
                                                       max=self.max, decimals=self.decimals)
         return [self.val, self.ok]
 

+ 2 - 1
ObjectUI.py

@@ -783,7 +783,8 @@ class GeometryObjectUI(ObjectUI):
             "cut and negative for 'inside' cut."
         )
         self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
-        self.tool_offset_entry = FCEntry()
+        self.tool_offset_entry = FloatEntry()
+        self.tool_offset_entry.setValidator(QtGui.QDoubleValidator(-9999.9999, 9999.9999, 4))
         spacer_lbl = QtWidgets.QLabel(" ")
         spacer_lbl.setFixedWidth(80)
 

+ 7 - 0
README.md

@@ -16,6 +16,13 @@ CAD program, and create G-Code for Isolation routing.
 - fixed errors in Toggle Axis
 - fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View
 - moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI
+- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
+- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
+- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
+- changed the shortcut key for Transform Tool from 'T' to 'ALT+T'
+- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
+- added Double Validator for the Offset value so only float numbers can be entered.
+- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab
 
 7.02.2019
 

+ 76 - 25
camlib.py

@@ -4939,25 +4939,25 @@ class CNCjob(Geometry):
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
 
-        self.tooldia = tooldia
-        self.z_cut = z_cut
-        self.z_move = z_move
+        self.tooldia = float(tooldia) if tooldia else None
+        self.z_cut = float(z_cut) if z_cut else None
+        self.z_move = float(z_move) if z_move else None
 
-        self.feedrate = feedrate
-        self.feedrate_z = feedrate_z
-        self.feedrate_rapid = feedrate_rapid
+        self.feedrate = float(feedrate) if feedrate else None
+        self.feedrate_z = float(feedrate_z) if feedrate_z else None
+        self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
 
-        self.spindlespeed = spindlespeed
+        self.spindlespeed = int(spindlespeed) if spindlespeed else None
         self.dwell = dwell
-        self.dwelltime = dwelltime
+        self.dwelltime = float(dwelltime) if dwelltime else None
 
-        self.startz = startz
-        self.endz = endz
+        self.startz = float(startz) if startz else None
+        self.endz = float(endz) if endz else None
 
-        self.depthpercut = depthpercut
+        self.depthpercut = float(depthpercut) if depthpercut else None
         self.multidepth = multidepth
 
-        self.toolchangez = toolchangez
+        self.toolchangez = float(toolchangez) if toolchangez else None
 
         try:
             if toolchangexy == '':
@@ -5120,14 +5120,57 @@ class CNCjob(Geometry):
                                  "from a Geometry object without solid_geometry.")
 
         temp_solid_geometry = []
+
+        def bounds_rec(obj):
+            if type(obj) is list:
+                minx = Inf
+                miny = Inf
+                maxx = -Inf
+                maxy = -Inf
+
+                for k in obj:
+                    if type(k) is dict:
+                        for key in k:
+                            minx_, miny_, maxx_, maxy_ = bounds_rec(k[key])
+                            minx = min(minx, minx_)
+                            miny = min(miny, miny_)
+                            maxx = max(maxx, maxx_)
+                            maxy = max(maxy, maxy_)
+                    else:
+                        minx_, miny_, maxx_, maxy_ = bounds_rec(k)
+                        minx = min(minx, minx_)
+                        miny = min(miny, miny_)
+                        maxx = max(maxx, maxx_)
+                        maxy = max(maxy, maxy_)
+                return minx, miny, maxx, maxy
+            else:
+                # it's a Shapely object, return it's bounds
+                return obj.bounds
+
         if offset != 0.0:
+            offset_for_use = offset
+
+            if offset <0:
+                a, b, c, d = bounds_rec(geometry.solid_geometry)
+                # if the offset is less than half of the total length or less than half of the total width of the
+                # solid geometry it's obvious we can't do the offset
+                if -offset > ((c - a) / 2) or -offset > ((d - b) / 2):
+                    self.app.inform.emit("[ERROR_NOTCL]The Tool Offset value is too negative to use "
+                                         "for the current_geometry.\n"
+                                         "Raise the value (in module) and try again.")
+                    return 'fail'
+                # hack: make offset smaller by 0.0000000001 which is insignificant difference but allow the job
+                # to continue
+                elif  -offset == ((c - a) / 2) or -offset == ((d - b) / 2):
+                    offset_for_use = offset - 0.0000000001
+
             for it in geometry.solid_geometry:
                 # if the geometry is a closed shape then create a Polygon out of it
                 if isinstance(it, LineString):
                     c = it.coords
                     if c[0] == c[-1]:
                         it = Polygon(it)
-                temp_solid_geometry.append(it.buffer(offset, join_style=2))
+                temp_solid_geometry.append(it.buffer(offset_for_use, join_style=2))
         else:
             temp_solid_geometry = geometry.solid_geometry
 
@@ -5135,25 +5178,33 @@ class CNCjob(Geometry):
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
 
-        self.tooldia = tooldia
-        self.z_cut = z_cut
-        self.z_move = z_move
+        self.tooldia = float(tooldia) if tooldia else None
 
-        self.feedrate = feedrate
-        self.feedrate_z = feedrate_z
-        self.feedrate_rapid = feedrate_rapid
+        self.z_cut = float(z_cut) if z_cut else None
+
+        self.z_move = float(z_move) if z_move else None
+
+        self.feedrate = float(feedrate) if feedrate else None
+
+        self.feedrate_z = float(feedrate_z) if feedrate_z else None
+
+        self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
+
+        self.spindlespeed = int(spindlespeed) if spindlespeed else None
 
-        self.spindlespeed = spindlespeed
         self.dwell = dwell
-        self.dwelltime = dwelltime
 
-        self.startz = startz
-        self.endz = endz
+        self.dwelltime = float(dwelltime) if dwelltime else None
+
+        self.startz = float(startz) if startz else None
+
+        self.endz = float(endz) if endz else None
+
+        self.depthpercut = float(depthpercut) if depthpercut else None
 
-        self.depthpercut = depthpercut
         self.multidepth = multidepth
 
-        self.toolchangez = toolchangez
+        self.toolchangez = float(toolchangez) if toolchangez else None
 
         try:
             if toolchangexy == '':

+ 1 - 0
flatcamTools/ToolNonCopperClear.py

@@ -5,6 +5,7 @@ from copy import copy,deepcopy
 from ObjectCollection import *
 import time
 
+
 class NonCopperClear(FlatCAMTool, Gerber):
 
     toolName = "Non-Copper Clearing"

BIN
share/letter_t_32.png