Просмотр исходного кода

Merged in test_beta_8.907 (pull request #132)

Test beta 8.907
Marius Stanciu 7 лет назад
Родитель
Сommit
c59da8669b

+ 1 - 1
FlatCAM.py

@@ -6,7 +6,6 @@ from FlatCAMApp import App
 from multiprocessing import freeze_support
 from multiprocessing import freeze_support
 import VisPyPatches
 import VisPyPatches
 
 
-
 if sys.platform == "win32":
 if sys.platform == "win32":
     # cx_freeze 'module win32' workaround
     # cx_freeze 'module win32' workaround
     import OpenGL.platform.win32
     import OpenGL.platform.win32
@@ -34,5 +33,6 @@ if __name__ == '__main__':
 
 
     app = QtWidgets.QApplication(sys.argv)
     app = QtWidgets.QApplication(sys.argv)
     fc = App()
     fc = App()
+
     sys.exit(app.exec_())
     sys.exit(app.exec_())
 
 

Разница между файлами не показана из-за своего большого размера
+ 438 - 267
FlatCAMApp.py


+ 113 - 401
FlatCAMEditor.py

@@ -106,21 +106,21 @@ class BufferSelectionTool(FlatCAMTool):
     def on_buffer(self):
     def on_buffer(self):
         buffer_distance = self.buffer_distance_entry.get_value()
         buffer_distance = self.buffer_distance_entry.get_value()
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
-        # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
+        # I populated the combobox such that the index coincide with the join styles value (which is really an INT)
         join_style = self.buffer_corner_cb.currentIndex() + 1
         join_style = self.buffer_corner_cb.currentIndex() + 1
         self.draw_app.buffer(buffer_distance, join_style)
         self.draw_app.buffer(buffer_distance, join_style)
 
 
     def on_buffer_int(self):
     def on_buffer_int(self):
         buffer_distance = self.buffer_distance_entry.get_value()
         buffer_distance = self.buffer_distance_entry.get_value()
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
-        # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
+        # I populated the combobox such that the index coincide with the join styles value (which is really an INT)
         join_style = self.buffer_corner_cb.currentIndex() + 1
         join_style = self.buffer_corner_cb.currentIndex() + 1
         self.draw_app.buffer_int(buffer_distance, join_style)
         self.draw_app.buffer_int(buffer_distance, join_style)
 
 
     def on_buffer_ext(self):
     def on_buffer_ext(self):
         buffer_distance = self.buffer_distance_entry.get_value()
         buffer_distance = self.buffer_distance_entry.get_value()
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
         # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
-        # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
+        # I populated the combobox such that the index coincide with the join styles value (which is really an INT)
         join_style = self.buffer_corner_cb.currentIndex() + 1
         join_style = self.buffer_corner_cb.currentIndex() + 1
         self.draw_app.buffer_ext(buffer_distance, join_style)
         self.draw_app.buffer_ext(buffer_distance, join_style)
 
 
@@ -583,6 +583,8 @@ class FCCircle(FCShapeTool):
 
 
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_circle'
+
         self.start_msg = "Click on CENTER ..."
         self.start_msg = "Click on CENTER ..."
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
         self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
 
 
@@ -620,6 +622,8 @@ class FCCircle(FCShapeTool):
 class FCArc(FCShapeTool):
 class FCArc(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_arc'
+
         self.start_msg = "Click on CENTER ..."
         self.start_msg = "Click on CENTER ..."
 
 
         # Direction of rotation between point 1 and 2.
         # Direction of rotation between point 1 and 2.
@@ -808,6 +812,8 @@ class FCRectangle(FCShapeTool):
 
 
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_rectangle'
+
         self.start_msg = "Click on 1st corner ..."
         self.start_msg = "Click on 1st corner ..."
 
 
     def click(self, point):
     def click(self, point):
@@ -846,6 +852,8 @@ class FCPolygon(FCShapeTool):
 
 
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_polygon'
+
         self.start_msg = "Click on 1st point ..."
         self.start_msg = "Click on 1st point ..."
 
 
     def click(self, point):
     def click(self, point):
@@ -891,6 +899,8 @@ class FCPath(FCPolygon):
 
 
     def make(self):
     def make(self):
         self.geometry = DrawToolShape(LineString(self.points))
         self.geometry = DrawToolShape(LineString(self.points))
+        self.name = 'fc_path'
+
         self.draw_app.in_action = False
         self.draw_app.in_action = False
         self.complete = True
         self.complete = True
         self.draw_app.app.inform.emit("[success]Done. Path completed.")
         self.draw_app.app.inform.emit("[success]Done. Path completed.")
@@ -912,6 +922,8 @@ class FCPath(FCPolygon):
 class FCSelect(DrawTool):
 class FCSelect(DrawTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_select'
+
         self.storage = self.draw_app.storage
         self.storage = self.draw_app.storage
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.selected = self.draw_app.selected
         # self.selected = self.draw_app.selected
@@ -989,6 +1001,7 @@ class FCSelect(DrawTool):
 class FCDrillSelect(DrawTool):
 class FCDrillSelect(DrawTool):
     def __init__(self, exc_editor_app):
     def __init__(self, exc_editor_app):
         DrawTool.__init__(self, exc_editor_app)
         DrawTool.__init__(self, exc_editor_app)
+        self.name = 'fc_drill_select'
 
 
         self.exc_editor_app = exc_editor_app
         self.exc_editor_app = exc_editor_app
         self.storage = self.exc_editor_app.storage_dict
         self.storage = self.exc_editor_app.storage_dict
@@ -1146,6 +1159,8 @@ class FCDrillSelect(DrawTool):
 class FCMove(FCShapeTool):
 class FCMove(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
+        self.name = 'fc_move'
+
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.shape_buffer = self.draw_app.shape_buffer
         self.origin = None
         self.origin = None
         self.destination = None
         self.destination = None
@@ -1211,6 +1226,9 @@ class FCMove(FCShapeTool):
 
 
 
 
 class FCCopy(FCMove):
 class FCCopy(FCMove):
+    def __init__(self, draw_app):
+        FCMove.__init__(self, draw_app)
+        self.name = 'fc_copy'
 
 
     def make(self):
     def make(self):
         # Create new geometry
         # Create new geometry
@@ -1225,6 +1243,8 @@ class FCCopy(FCMove):
 class FCText(FCShapeTool):
 class FCText(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
+        self.name = 'fc_text'
+
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.shape_buffer = self.draw_app.shape_buffer
         self.draw_app = draw_app
         self.draw_app = draw_app
         self.app = draw_app.app
         self.app = draw_app.app
@@ -1275,6 +1295,8 @@ class FCText(FCShapeTool):
 class FCBuffer(FCShapeTool):
 class FCBuffer(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
+        self.name = 'fc_buffer'
+
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.shape_buffer = self.draw_app.shape_buffer
         self.draw_app = draw_app
         self.draw_app = draw_app
         self.app = draw_app.app
         self.app = draw_app.app
@@ -1341,6 +1363,8 @@ class FCBuffer(FCShapeTool):
 class FCPaint(FCShapeTool):
 class FCPaint(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
+        self.name = 'fc_paint'
+
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.shape_buffer = self.draw_app.shape_buffer
         self.draw_app = draw_app
         self.draw_app = draw_app
         self.app = draw_app.app
         self.app = draw_app.app
@@ -1355,6 +1379,7 @@ class FCPaint(FCShapeTool):
 class FCRotate(FCShapeTool):
 class FCRotate(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         FCShapeTool.__init__(self, draw_app)
         FCShapeTool.__init__(self, draw_app)
+        self.name = 'fc_rotate'
 
 
         geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
         geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
 
 
@@ -1366,7 +1391,6 @@ class FCRotate(FCShapeTool):
     def set_origin(self, origin):
     def set_origin(self, origin):
         self.origin = origin
         self.origin = origin
 
 
-
     def make(self):
     def make(self):
         # Create new geometry
         # Create new geometry
         # dx = self.origin[0]
         # dx = self.origin[0]
@@ -1382,9 +1406,9 @@ class FCRotate(FCShapeTool):
         #self.draw_app.select_tool("select")
         #self.draw_app.select_tool("select")
 
 
     def on_key(self, key):
     def on_key(self, key):
-        if key == 'Enter':
-            if self.complete == True:
-                self.make()
+        if key == 'Enter' or key == QtCore.Qt.Key_Enter:
+            self.make()
+            return "Done"
 
 
     def click(self, point):
     def click(self, point):
         self.make()
         self.make()
@@ -1408,6 +1432,7 @@ class FCDrillAdd(FCShapeTool):
 
 
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_drill_add'
 
 
         self.selected_dia = None
         self.selected_dia = None
         try:
         try:
@@ -1443,11 +1468,17 @@ class FCDrillAdd(FCShapeTool):
         return DrawToolUtilityShape(self.util_shape(data))
         return DrawToolUtilityShape(self.util_shape(data))
 
 
     def util_shape(self, point):
     def util_shape(self, point):
+        if point[0] is None and point[1] is None:
+            point_x = self.draw_app.x
+            point_y = self.draw_app.y
+        else:
+            point_x = point[0]
+            point_y = point[1]
 
 
-        start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1])
-        stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1])
-        start_vert_line = (point[0], (point[1] - (self.selected_dia / 2)))
-        stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2)))
+        start_hor_line = ((point_x - (self.selected_dia / 2)), point_y)
+        stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y)
+        start_vert_line = (point_x, (point_y - (self.selected_dia / 2)))
+        stop_vert_line = (point_x, (point_y + (self.selected_dia / 2)))
 
 
         return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)])
         return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)])
 
 
@@ -1473,6 +1504,7 @@ class FCDrillArray(FCShapeTool):
 
 
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_drill_array'
 
 
         self.draw_app.array_frame.show()
         self.draw_app.array_frame.show()
 
 
@@ -1558,28 +1590,27 @@ class FCDrillArray(FCShapeTool):
             return
             return
 
 
         if self.drill_array == 'Linear':
         if self.drill_array == 'Linear':
-            # if self.origin is None:
-            #     self.origin = (0, 0)
-            #
-            # dx = data[0] - self.origin[0]
-            # dy = data[1] - self.origin[1]
-            dx = data[0]
-            dy = data[1]
+            if data[0] is None and data[1] is None:
+                dx = self.draw_app.x
+                dy = self.draw_app.y
+            else:
+                dx = data[0]
+                dy = data[1]
 
 
             geo_list = []
             geo_list = []
             geo = None
             geo = None
-            self.points = data
+            self.points = [dx, dy]
 
 
             for item in range(self.drill_array_size):
             for item in range(self.drill_array_size):
                 if self.drill_axis == 'X':
                 if self.drill_axis == 'X':
-                    geo = self.util_shape(((data[0] + (self.drill_pitch * item)), data[1]))
+                    geo = self.util_shape(((dx + (self.drill_pitch * item)), dy))
                 if self.drill_axis == 'Y':
                 if self.drill_axis == 'Y':
-                    geo = self.util_shape((data[0], (data[1] + (self.drill_pitch * item))))
+                    geo = self.util_shape((dx, (dy + (self.drill_pitch * item))))
                 if self.drill_axis == 'A':
                 if self.drill_axis == 'A':
                     x_adj = self.drill_pitch * math.cos(math.radians(self.drill_linear_angle))
                     x_adj = self.drill_pitch * math.cos(math.radians(self.drill_linear_angle))
                     y_adj = self.drill_pitch * math.sin(math.radians(self.drill_linear_angle))
                     y_adj = self.drill_pitch * math.sin(math.radians(self.drill_linear_angle))
                     geo = self.util_shape(
                     geo = self.util_shape(
-                        ((data[0] + (x_adj * item)), (data[1] + (y_adj * item)))
+                        ((dx + (x_adj * item)), (dy + (y_adj * item)))
                     )
                     )
 
 
                 if static is None or static is False:
                 if static is None or static is False:
@@ -1592,16 +1623,30 @@ class FCDrillArray(FCShapeTool):
             self.last_dy = dy
             self.last_dy = dy
             return DrawToolUtilityShape(geo_list)
             return DrawToolUtilityShape(geo_list)
         else:
         else:
+            if data[0] is None and data[1] is None:
+                cdx = self.draw_app.x
+                cdy = self.draw_app.y
+            else:
+                cdx = data[0]
+                cdy = data[1]
+
             if len(self.pt) > 0:
             if len(self.pt) > 0:
                 temp_points = [x for x in self.pt]
                 temp_points = [x for x in self.pt]
-                temp_points.append(data)
+                temp_points.append([cdx, cdy])
                 return DrawToolUtilityShape(LineString(temp_points))
                 return DrawToolUtilityShape(LineString(temp_points))
 
 
     def util_shape(self, point):
     def util_shape(self, point):
-        start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1])
-        stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1])
-        start_vert_line = (point[0], (point[1] - (self.selected_dia / 2)))
-        stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2)))
+        if point[0] is None and point[1] is None:
+            point_x = self.draw_app.x
+            point_y = self.draw_app.y
+        else:
+            point_x = point[0]
+            point_y = point[1]
+
+        start_hor_line = ((point_x - (self.selected_dia / 2)), point_y)
+        stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y)
+        start_vert_line = (point_x, (point_y - (self.selected_dia / 2)))
+        stop_vert_line = (point_x, (point_y + (self.selected_dia / 2)))
 
 
         return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)])
         return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)])
 
 
@@ -1656,10 +1701,12 @@ class FCDrillArray(FCShapeTool):
         self.draw_app.array_frame.hide()
         self.draw_app.array_frame.hide()
         return
         return
 
 
-class FCDrillResize(FCShapeTool):
 
 
+class FCDrillResize(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_drill_resize'
+
         self.draw_app.app.inform.emit("Click on the Drill(s) to resize ...")
         self.draw_app.app.inform.emit("Click on the Drill(s) to resize ...")
         self.resize_dia = None
         self.resize_dia = None
         self.draw_app.resize_frame.show()
         self.draw_app.resize_frame.show()
@@ -1761,6 +1808,8 @@ class FCDrillResize(FCShapeTool):
 class FCDrillMove(FCShapeTool):
 class FCDrillMove(FCShapeTool):
     def __init__(self, draw_app):
     def __init__(self, draw_app):
         DrawTool.__init__(self, draw_app)
         DrawTool.__init__(self, draw_app)
+        self.name = 'fc_drill_move'
+
         # self.shape_buffer = self.draw_app.shape_buffer
         # self.shape_buffer = self.draw_app.shape_buffer
         self.origin = None
         self.origin = None
         self.destination = None
         self.destination = None
@@ -1850,6 +1899,9 @@ class FCDrillMove(FCShapeTool):
 
 
 
 
 class FCDrillCopy(FCDrillMove):
 class FCDrillCopy(FCDrillMove):
+    def __init__(self, draw_app):
+        FCDrillMove.__init__(self, draw_app)
+        self.name = 'fc_drill_copy'
 
 
     def make(self):
     def make(self):
         # Create new geometry
         # Create new geometry
@@ -1977,6 +2029,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.geo_key_modifiers = None
         self.geo_key_modifiers = None
         self.x = None  # Current mouse cursor pos
         self.x = None  # Current mouse cursor pos
         self.y = None
         self.y = None
+
         # Current snapped mouse pos
         # Current snapped mouse pos
         self.snap_x = None
         self.snap_x = None
         self.snap_y = None
         self.snap_y = None
@@ -2083,16 +2136,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
         self.app.ui.geo_edit_toolbar.setDisabled(True)
         self.app.ui.geo_edit_toolbar.setDisabled(True)
 
 
         settings = QSettings("Open Source", "FlatCAM")
         settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("theme"):
-            theme = settings.value('theme', type=str)
-            if theme == 'standard':
+        if settings.contains("layout"):
+            layout = settings.value('layout', type=str)
+            if layout == 'standard':
                 # self.app.ui.geo_edit_toolbar.setVisible(False)
                 # self.app.ui.geo_edit_toolbar.setVisible(False)
 
 
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
                 self.app.ui.corner_snap_btn.setEnabled(False)
                 self.app.ui.corner_snap_btn.setEnabled(False)
                 self.app.ui.snap_magnet.setVisible(False)
                 self.app.ui.snap_magnet.setVisible(False)
                 self.app.ui.corner_snap_btn.setVisible(False)
                 self.app.ui.corner_snap_btn.setVisible(False)
-            elif theme == 'compact':
+            elif layout == 'compact':
                 # self.app.ui.geo_edit_toolbar.setVisible(True)
                 # self.app.ui.geo_edit_toolbar.setVisible(True)
 
 
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
@@ -2134,35 +2187,30 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
 
         # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
         # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
         # but those from FlatCAMGeoEditor
         # but those from FlatCAMGeoEditor
-        self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot)
+
         self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
-        self.app.collection.view.keyPressed.disconnect()
+
         self.app.collection.view.clicked.disconnect()
         self.app.collection.view.clicked.disconnect()
 
 
         self.canvas.vis_connect('mouse_press', self.on_canvas_click)
         self.canvas.vis_connect('mouse_press', self.on_canvas_click)
         self.canvas.vis_connect('mouse_move', self.on_canvas_move)
         self.canvas.vis_connect('mouse_move', self.on_canvas_move)
         self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
         self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
-        self.canvas.vis_connect('key_press', self.on_canvas_key)
-        self.canvas.vis_connect('key_release', self.on_canvas_key_release)
+
 
 
     def disconnect_canvas_event_handlers(self):
     def disconnect_canvas_event_handlers(self):
 
 
         self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
         self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
         self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
         self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
         self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
         self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
-        self.canvas.vis_disconnect('key_press', self.on_canvas_key)
-        self.canvas.vis_disconnect('key_release', self.on_canvas_key_release)
 
 
         # we restore the key and mouse control to FlatCAMApp method
         # we restore the key and mouse control to FlatCAMApp method
-        self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
         self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
-        self.app.collection.view.keyPressed.connect(self.app.collection.on_key)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
 
     def add_shape(self, shape):
     def add_shape(self, shape):
@@ -2572,211 +2620,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
 
             self.tool_shape.redraw()
             self.tool_shape.redraw()
 
 
-    def on_canvas_key(self, event):
-        """
-        event.key has the key.
-
-        :param event:
-        :return:
-        """
-        self.key = event.key.name
-        self.geo_key_modifiers = QtWidgets.QApplication.keyboardModifiers()
-
-        if self.geo_key_modifiers == Qt.ControlModifier:
-            # save (update) the current geometry and return to the App
-            if self.key == 'S':
-                self.app.editor2object()
-                return
-
-            # toggle the measurement tool
-            if self.key == 'M':
-                self.app.measurement_tool.run()
-                return
-
-        # Finish the current action. Use with tools that do not
-        # complete automatically, like a polygon or path.
-        if event.key.name == 'Enter':
-            if isinstance(self.active_tool, FCShapeTool):
-                self.active_tool.click(self.snap(self.x, self.y))
-                self.active_tool.make()
-                if self.active_tool.complete:
-                    self.on_shape_complete()
-                    self.app.inform.emit("[success]Done.")
-                # automatically make the selection tool active after completing current action
-                self.select_tool('select')
-            return
-
-        # Abort the current action
-        if event.key.name == 'Escape':
-            # TODO: ...?
-            # self.on_tool_select("select")
-            self.app.inform.emit("[WARNING_NOTCL]Cancelled.")
-
-            self.delete_utility_geometry()
-
-            self.replot()
-            # self.select_btn.setChecked(True)
-            # self.on_tool_select('select')
-            self.select_tool('select')
-            return
-
-        # Delete selected object
-        if event.key.name == 'Delete':
-            self.delete_selected()
-            self.replot()
-
-        # Move
-        if event.key.name == 'Space':
-            self.app.ui.geo_rotate_btn.setChecked(True)
-            self.on_tool_select('rotate')
-            self.active_tool.set_origin(self.snap(self.x, self.y))
-
-        if event.key == '1':
-            self.app.on_zoom_fit(None)
-
-        if event.key == '2':
-            self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
-
-        if event.key == '3':
-            self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
-
-        # Arc Tool
-        if event.key.name == 'A':
-            self.select_tool('arc')
-
-        # Buffer
-        if event.key.name == 'B':
-            self.select_tool('buffer')
-
-        # Copy
-        if event.key.name == 'C':
-            self.app.ui.geo_copy_btn.setChecked(True)
-            self.on_tool_select('copy')
-            self.active_tool.set_origin(self.snap(self.x, self.y))
-            self.app.inform.emit("Click on target point.")
-
-        # Substract Tool
-        if event.key.name == 'E':
-            if self.get_selected() is not None:
-                self.intersection()
-            else:
-                msg = "Please select geometry items \n" \
-                      "on which to perform Intersection Tool."
-
-                messagebox =QtWidgets.QMessageBox()
-                messagebox.setText(msg)
-                messagebox.setWindowTitle("Warning")
-                messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
-                messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
-                messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
-                messagebox.exec_()
-
-        # Grid Snap
-        if event.key.name == 'G':
-            self.app.ui.grid_snap_btn.trigger()
-
-            # make sure that the cursor shape is enabled/disabled, too
-            if self.options['grid_snap'] is True:
-                self.app.app_cursor.enabled = True
-            else:
-                self.app.app_cursor.enabled = False
-
-        # Paint
-        if event.key.name == 'I':
-            self.select_tool('paint')
-
-        # Corner Snap
-        if event.key.name == 'K':
-            self.on_corner_snap()
-
-        # Move
-        if event.key.name == 'M':
-            self.on_move_click()
-
-        # Polygon Tool
-        if event.key.name == 'N':
-            self.select_tool('polygon')
-
-        # Circle Tool
-        if event.key.name == 'O':
-            self.select_tool('circle')
-
-        # Path Tool
-        if event.key.name == 'P':
-            self.select_tool('path')
-
-        # Rectangle Tool
-        if event.key.name == 'R':
-            self.select_tool('rectangle')
-
-        # Substract Tool
-        if event.key.name == 'S':
-            if self.get_selected() is not None:
-                self.subtract()
-            else:
-                msg = "Please select geometry items \n" \
-                      "on which to perform Substraction Tool."
-
-                messagebox =QtWidgets.QMessageBox()
-                messagebox.setText(msg)
-                messagebox.setWindowTitle("Warning")
-                messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
-                messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
-                messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
-                messagebox.exec_()
-
-        # Add Text Tool
-        if event.key.name == 'T':
-            self.select_tool('text')
-
-        # Substract Tool
-        if event.key.name == 'U':
-            if self.get_selected() is not None:
-                self.union()
-            else:
-                msg = "Please select geometry items \n" \
-                      "on which to perform union."
-
-                messagebox =QtWidgets.QMessageBox()
-                messagebox.setText(msg)
-                messagebox.setWindowTitle("Warning")
-                messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
-                messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
-                messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
-                messagebox.exec_()
-
-        # Cut Action Tool
-        if event.key.name == 'X':
-            if self.get_selected() is not None:
-                self.cutpath()
-            else:
-                msg = 'Please first select a geometry item to be cutted\n' \
-                      'then select the geometry item that will be cutted\n' \
-                      'out of the first item. In the end press ~X~ key or\n' \
-                      'the toolbar button.' \
-
-                messagebox =QtWidgets.QMessageBox()
-                messagebox.setText(msg)
-                messagebox.setWindowTitle("Warning")
-                messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
-                messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
-                messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
-                messagebox.exec_()
-
-        # Propagate to tool
-        response = None
-        if self.active_tool is not None:
-            response = self.active_tool.on_key(event.key)
-        if response is not None:
-            self.app.inform.emit(response)
-
-        # Show Shortcut list
-        if event.key.name == '`':
-            self.app.on_shortcut_list()
-
-    def on_canvas_key_release(self, event):
-        self.key = None
-
     def on_delete_btn(self):
     def on_delete_btn(self):
         self.delete_selected()
         self.delete_selected()
         self.replot()
         self.replot()
@@ -3465,7 +3308,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         grid1.addWidget(addtool_entry_lbl, 0, 0)
         grid1.addWidget(addtool_entry_lbl, 0, 0)
 
 
         hlay = QtWidgets.QHBoxLayout()
         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)
         hlay.addWidget(self.addtool_entry)
 
 
         self.addtool_btn = QtWidgets.QPushButton('Add Tool')
         self.addtool_btn = QtWidgets.QPushButton('Add Tool')
@@ -3873,7 +3717,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         if self.units == "IN":
         if self.units == "IN":
             self.addtool_entry.set_value(0.039)
             self.addtool_entry.set_value(0.039)
         else:
         else:
-            self.addtool_entry.set_value(1)
+            self.addtool_entry.set_value(1.00)
 
 
         sort_temp = []
         sort_temp = []
 
 
@@ -4057,9 +3901,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
         # 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)
         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
         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:
         if tool_dia not in self.olddia_newdia:
             storage_elem = FlatCAMGeoEditor.make_storage()
             storage_elem = FlatCAMGeoEditor.make_storage()
@@ -4224,16 +4080,16 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.app.ui.exc_edit_toolbar.setDisabled(True)
         self.app.ui.exc_edit_toolbar.setDisabled(True)
 
 
         settings = QSettings("Open Source", "FlatCAM")
         settings = QSettings("Open Source", "FlatCAM")
-        if settings.contains("theme"):
-            theme = settings.value('theme', type=str)
-            if theme == 'standard':
+        if settings.contains("layout"):
+            layout = settings.value('layout', type=str)
+            if layout == 'standard':
                 # self.app.ui.exc_edit_toolbar.setVisible(False)
                 # self.app.ui.exc_edit_toolbar.setVisible(False)
 
 
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
                 self.app.ui.corner_snap_btn.setEnabled(False)
                 self.app.ui.corner_snap_btn.setEnabled(False)
                 self.app.ui.snap_magnet.setVisible(False)
                 self.app.ui.snap_magnet.setVisible(False)
                 self.app.ui.corner_snap_btn.setVisible(False)
                 self.app.ui.corner_snap_btn.setVisible(False)
-            elif theme == 'compact':
+            elif layout == 'compact':
                 # self.app.ui.exc_edit_toolbar.setVisible(True)
                 # self.app.ui.exc_edit_toolbar.setVisible(True)
 
 
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
                 self.app.ui.snap_max_dist_entry.setEnabled(False)
@@ -4277,35 +4133,27 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
         # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
         # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
         # but those from FlatCAMGeoEditor
         # but those from FlatCAMGeoEditor
-        self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot)
+
         self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
         self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
-        self.app.collection.view.keyPressed.disconnect()
         self.app.collection.view.clicked.disconnect()
         self.app.collection.view.clicked.disconnect()
 
 
         self.canvas.vis_connect('mouse_press', self.on_canvas_click)
         self.canvas.vis_connect('mouse_press', self.on_canvas_click)
         self.canvas.vis_connect('mouse_move', self.on_canvas_move)
         self.canvas.vis_connect('mouse_move', self.on_canvas_move)
         self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
         self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
-        self.canvas.vis_connect('key_press', self.on_canvas_key)
-        self.canvas.vis_connect('key_release', self.on_canvas_key_release)
 
 
     def disconnect_canvas_event_handlers(self):
     def disconnect_canvas_event_handlers(self):
-
         self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
         self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
         self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
         self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
         self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
         self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
-        self.canvas.vis_disconnect('key_press', self.on_canvas_key)
-        self.canvas.vis_disconnect('key_release', self.on_canvas_key_release)
 
 
         # we restore the key and mouse control to FlatCAMApp method
         # we restore the key and mouse control to FlatCAMApp method
-        self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
         self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
         self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
-        self.app.collection.view.keyPressed.connect(self.app.collection.on_key)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
         self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
 
 
     def clear(self):
     def clear(self):
@@ -4374,6 +4222,9 @@ class FlatCAMExcEditor(QtCore.QObject):
 
 
         self.replot()
         self.replot()
 
 
+        # add a first tool in the Tool Table
+        self.on_tool_add(tooldia=1.00)
+
     def update_fcexcellon(self, exc_obj):
     def update_fcexcellon(self, exc_obj):
         """
         """
         Create a new Excellon object that contain the edited content of the source Excellon object
         Create a new Excellon object that contain the edited content of the source Excellon object
@@ -4871,145 +4722,6 @@ class FlatCAMExcEditor(QtCore.QObject):
         # Update cursor
         # Update cursor
         self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
         self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
 
 
-
-    def on_canvas_key(self, event):
-        """
-        event.key has the key.
-
-        :param event:
-        :return:
-        """
-        self.key = event.key.name
-        self.modifiers = QtWidgets.QApplication.keyboardModifiers()
-
-        if self.modifiers == Qt.ControlModifier:
-            # save (update) the current geometry and return to the App
-            if self.key == 'S':
-                self.app.editor2object()
-                return
-
-            # toggle the measurement tool
-            if self.key == 'M':
-                self.app.measurement_tool.run()
-                return
-
-        # Abort the current action
-        if event.key.name == 'Escape':
-            # TODO: ...?
-            # self.on_tool_select("select")
-            self.app.inform.emit("[WARNING_NOTCL]Cancelled.")
-
-            self.delete_utility_geometry()
-
-            self.replot()
-            # self.select_btn.setChecked(True)
-            # self.on_tool_select('select')
-            self.select_tool('select')
-            return
-
-        # Delete selected object
-        if event.key.name == 'Delete':
-            self.launched_from_shortcuts = True
-            if self.selected:
-                self.delete_selected()
-                self.replot()
-            else:
-                self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.")
-            return
-
-        if event.key == '1':
-            self.launched_from_shortcuts = True
-            self.app.on_zoom_fit(None)
-
-        if event.key == '2':
-            self.launched_from_shortcuts = True
-            self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
-
-        if event.key == '3':
-            self.launched_from_shortcuts = True
-            self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
-
-        # Add Array of Drill Hole Tool
-        if event.key.name == 'A':
-            self.launched_from_shortcuts = True
-            self.app.inform.emit("Click on target point.")
-            self.app.ui.add_drill_array_btn.setChecked(True)
-            self.select_tool('add_array')
-            return
-
-        # Copy
-        if event.key.name == 'C':
-            self.launched_from_shortcuts = True
-            if self.selected:
-                self.app.inform.emit("Click on target point.")
-                self.app.ui.copy_drill_btn.setChecked(True)
-                self.on_tool_select('copy')
-                self.active_tool.set_origin((self.snap_x, self.snap_y))
-            else:
-                self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to copy.")
-            return
-
-        # Add Drill Hole Tool
-        if event.key.name == 'D':
-            self.launched_from_shortcuts = True
-            self.app.inform.emit("Click on target point.")
-            self.app.ui.add_drill_btn.setChecked(True)
-            self.select_tool('add')
-            return
-
-        # Grid Snap
-        if event.key.name == 'G':
-            self.launched_from_shortcuts = True
-            # make sure that the cursor shape is enabled/disabled, too
-            if self.options['grid_snap'] is True:
-                self.app.app_cursor.enabled = False
-            else:
-                self.app.app_cursor.enabled = True
-            self.app.ui.grid_snap_btn.trigger()
-            return
-
-        # Corner Snap
-        if event.key.name == 'K':
-            self.launched_from_shortcuts = True
-            self.app.ui.corner_snap_btn.trigger()
-            return
-
-        # Move
-        if event.key.name == 'M':
-            self.launched_from_shortcuts = True
-            if self.selected:
-                self.app.inform.emit("Click on target point.")
-                self.app.ui.move_drill_btn.setChecked(True)
-                self.on_tool_select('move')
-                self.active_tool.set_origin((self.snap_x, self.snap_y))
-            else:
-                self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to move.")
-            return
-
-        # Resize Tool
-        if event.key.name == 'R':
-            self.launched_from_shortcuts = True
-            self.select_tool('resize')
-            return
-
-        # Select Tool
-        if event.key.name == 'S':
-            self.launched_from_shortcuts = True
-            self.select_tool('select')
-            return
-
-        # Propagate to tool
-        response = None
-        if self.active_tool is not None:
-            response = self.active_tool.on_key(event.key)
-        if response is not None:
-            self.app.inform.emit(response)
-
-        # Show Shortcut list
-        if event.key.name == '`':
-            self.app.on_shortcut_list()
-            return
-
     def on_canvas_key_release(self, event):
     def on_canvas_key_release(self, event):
         self.key = None
         self.key = None
 
 

Разница между файлами не показана из-за своего большого размера
+ 746 - 44
FlatCAMGUI.py


+ 136 - 81
FlatCAMObj.py

@@ -388,7 +388,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
         grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
 
 
     def __init__(self, name):
     def __init__(self, name):
-        Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"])
+        Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"]))
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
         self.kind = "gerber"
         self.kind = "gerber"
@@ -480,7 +480,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
 
         def geo_init(geo_obj, app_obj):
         def geo_init(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
             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"]:
             if not self.options["noncopperrounded"]:
                 bounding_box = bounding_box.envelope
                 bounding_box = bounding_box.envelope
             non_copper = bounding_box.difference(self.solid_geometry)
             non_copper = bounding_box.difference(self.solid_geometry)
@@ -497,7 +497,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         def geo_init(geo_obj, app_obj):
         def geo_init(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
             assert isinstance(geo_obj, FlatCAMGeometry)
             # Bounding box with rounded corners
             # 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
             if not self.options["bboxrounded"]:  # Remove rounded corners
                 bounding_box = bounding_box.envelope
                 bounding_box = bounding_box.envelope
             geo_obj.solid_geometry = bounding_box
             geo_obj.solid_geometry = bounding_box
@@ -557,7 +557,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
 
         def follow_init(follow_obj, app):
         def follow_init(follow_obj, app):
             # Propagate options
             # Propagate options
-            follow_obj.options["cnctooldia"] = self.options["isotooldia"]
+            follow_obj.options["cnctooldia"] = float(self.options["isotooldia"])
             follow_obj.solid_geometry = self.solid_geometry
             follow_obj.solid_geometry = self.solid_geometry
 
 
         # TODO: Do something if this is None. Offer changing name?
         # TODO: Do something if this is None. Offer changing name?
@@ -579,11 +579,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         :return: None
         """
         """
         if dia is None:
         if dia is None:
-            dia = self.options["isotooldia"]
+            dia = float(self.options["isotooldia"])
         if passes is None:
         if passes is None:
             passes = int(self.options["isopasses"])
             passes = int(self.options["isopasses"])
         if overlap is None:
         if overlap is None:
-            overlap = self.options["isooverlap"]
+            overlap = float(self.options["isooverlap"])
         if combine is None:
         if combine is None:
             combine = self.options["combine_passes"]
             combine = self.options["combine_passes"]
         else:
         else:
@@ -638,7 +638,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             # TODO: This is ugly. Create way to pass data into init function.
             # TODO: This is ugly. Create way to pass data into init function.
             def iso_init(geo_obj, app_obj):
             def iso_init(geo_obj, app_obj):
                 # Propagate options
                 # Propagate options
-                geo_obj.options["cnctooldia"] = self.options["isotooldia"]
+                geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
                 geo_obj.solid_geometry = []
                 geo_obj.solid_geometry = []
                 for i in range(passes):
                 for i in range(passes):
                     iso_offset = (((2 * i + 1) / 2.0) * dia) - (i * overlap * dia)
                     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.
                 # TODO: This is ugly. Create way to pass data into init function.
                 def iso_init(geo_obj, app_obj):
                 def iso_init(geo_obj, app_obj):
                     # Propagate options
                     # 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 is climb then the move is counter-clockwise around features
                     if milling_type == 'cl':
                     if milling_type == 'cl':
@@ -753,8 +753,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
 
         factor = Gerber.convert_units(self, units)
         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):
     def plot(self, **kwargs):
         """
         """
@@ -833,7 +833,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     optionChanged = QtCore.pyqtSignal(str)
     optionChanged = QtCore.pyqtSignal(str)
 
 
     def __init__(self, name):
     def __init__(self, name):
-        Excellon.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"])
+        Excellon.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"]))
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
         self.kind = "excellon"
         self.kind = "excellon"
@@ -1458,7 +1458,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             outname = self.options["name"] + "_mill"
             outname = self.options["name"] + "_mill"
 
 
         if tooldia is None:
         if tooldia is None:
-            tooldia = self.options["tooldia"]
+            tooldia = float(self.options["tooldia"])
 
 
         # Sort tools by diameter. items() -> [('name', diameter), ...]
         # Sort tools by diameter. items() -> [('name', diameter), ...]
         # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
         # 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"
             outname = self.options["name"] + "_mill"
 
 
         if tooldia is None:
         if tooldia is None:
-            tooldia = self.options["slot_tooldia"]
+            tooldia = float(self.options["slot_tooldia"])
 
 
         # Sort tools by diameter. items() -> [('name', diameter), ...]
         # Sort tools by diameter. items() -> [('name', diameter), ...]
         # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
         # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
@@ -1564,7 +1564,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             return False, "Error: No tools."
             return False, "Error: No tools."
 
 
         for tool in tools:
         for tool in tools:
-            if tooldia > self.tools[tool]["C"]:
+            # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
+            adj_toolstable_tooldia = float('%.4f' % float(tooldia))
+            adj_file_tooldia = float('%.4f' % float(self.tools[tool]["C"]))
+            if adj_toolstable_tooldia > adj_file_tooldia + 0.0001:
                 self.app.inform.emit("[ERROR_NOTCL] Milling tool for SLOTS is larger than hole size. Cancelled.")
                 self.app.inform.emit("[ERROR_NOTCL] Milling tool for SLOTS is larger than hole size. Cancelled.")
                 return False, "Error: Milling tool is larger than hole."
                 return False, "Error: Milling tool is larger than hole."
 
 
@@ -1590,20 +1593,25 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero"
             # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero"
             for slot in self.slots:
             for slot in self.slots:
                 if slot['tool'] in tools:
                 if slot['tool'] in tools:
-                    buffer_value = self.tools[slot['tool']]["C"] / 2 - tooldia / 2
+                    toolstable_tool = float('%.4f' % float(tooldia))
+                    file_tool = float('%.4f' % float(self.tools[tool]["C"]))
+
+                    # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
+                    # for the file_tool (tooldia actually)
+                    buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001
                     if buffer_value == 0:
                     if buffer_value == 0:
                         start = slot['start']
                         start = slot['start']
                         stop = slot['stop']
                         stop = slot['stop']
 
 
                         lines_string = LineString([start, stop])
                         lines_string = LineString([start, stop])
-                        poly = lines_string.buffer(0.0000001, self.geo_steps_per_circle).exterior
+                        poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior
                         geo_obj.solid_geometry.append(poly)
                         geo_obj.solid_geometry.append(poly)
                     else:
                     else:
                         start = slot['start']
                         start = slot['start']
                         stop = slot['stop']
                         stop = slot['stop']
 
 
                         lines_string = LineString([start, stop])
                         lines_string = LineString([start, stop])
-                        poly = lines_string.buffer(buffer_value, self.geo_steps_per_circle).exterior
+                        poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior
                         geo_obj.solid_geometry.append(poly)
                         geo_obj.solid_geometry.append(poly)
 
 
         if use_thread:
         if use_thread:
@@ -1685,15 +1693,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             job_obj.options['ppname_e'] = pp_excellon_name
             job_obj.options['ppname_e'] = pp_excellon_name
 
 
             app_obj.progress.emit(20)
             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"]) if self.options["spindlespeed"] else None
             job_obj.dwell = self.options["dwell"]
             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.pp_excellon_name = pp_excellon_name
-            job_obj.toolchange_xy = self.app.defaults["excellon_toolchangexy"]
+
             job_obj.toolchange_xy_type = "excellon"
             job_obj.toolchange_xy_type = "excellon"
             job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
             job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
             job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
             job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
@@ -1729,14 +1738,18 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             # job_obj.options["tooldia"] =
             # job_obj.options["tooldia"] =
 
 
             tools_csv = ','.join(tools)
             tools_csv = ','.join(tools)
-            job_obj.generate_from_excellon_by_tool(self, tools_csv,
-                                                   drillz=self.options['drillz'],
-                                                   toolchange=self.options["toolchange"],
-                                                   toolchangez=self.options["toolchangez"],
-                                                   startz=self.options["startz"],
-                                                   endz=self.options["endz"],
-                                                   excellon_optimization_type=self.options["optimization_type"])
-
+            ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv,
+                                                             drillz=float(self.options['drillz']),
+                                                             toolchange=float(self.options["toolchange"]),
+                                                             toolchangexy=self.app.defaults["excellon_toolchangexy"],
+                                                             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':
+                return 'fail'
             app_obj.progress.emit(50)
             app_obj.progress.emit(50)
             job_obj.gcode_parse()
             job_obj.gcode_parse()
 
 
@@ -1772,11 +1785,11 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     def convert_units(self, units):
     def convert_units(self, units):
         factor = Excellon.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"] == '':
         if self.app.defaults["excellon_toolchangexy"] == '':
             self.options['toolchangexy'] = "0.0, 0.0"
             self.options['toolchangexy'] = "0.0, 0.0"
@@ -1791,8 +1804,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
             self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
 
 
         if self.options['startz'] is not None:
         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):
     def plot(self):
 
 
@@ -1967,7 +1980,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
     def __init__(self, name):
     def __init__(self, name):
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
-        Geometry.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"])
+        Geometry.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"]))
 
 
         self.kind = "geometry"
         self.kind = "geometry"
 
 
@@ -2260,7 +2273,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         if not self.tools:
         if not self.tools:
             self.tools.update({
             self.tools.update({
                 self.tooluid: {
                 self.tooluid: {
-                    'tooldia': self.options["cnctooldia"],
+                    'tooldia': float(self.options["cnctooldia"]),
                     'offset': 'Path',
                     'offset': 'Path',
                     'offset_value': 0.0,
                     'offset_value': 0.0,
                     'type': 'Rough',
                     'type': 'Rough',
@@ -2805,8 +2818,28 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.cutz_entry.setDisabled(False)
             self.ui.cutz_entry.setDisabled(False)
 
 
     def update_cutz(self):
     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()
         row = self.ui.geo_tools_table.currentRow()
         tool_uid = int(self.ui.geo_tools_table.item(row, 5).text())
         tool_uid = int(self.ui.geo_tools_table.item(row, 5).text())
@@ -3125,10 +3158,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
         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'])
         segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
 
 
-        xmin = self.options['xmin']
-        ymin = self.options['ymin']
-        xmax = self.options['xmax']
-        ymax = self.options['ymax']
+        try:
+            xmin = self.options['xmin']
+            ymin = self.options['ymin']
+            xmax = self.options['xmax']
+            ymax = self.options['ymax']
+        except Exception as e:
+            log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e))
+            msg = "[ERROR] An internal error has ocurred. See shell.\n"
+            msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e)
+            msg += traceback.format_exc()
+            self.app.inform.emit(msg)
+            return
 
 
         # Object initialization function for app.new_object()
         # Object initialization function for app.new_object()
         # RUNNING ON SEPARATE THREAD!
         # RUNNING ON SEPARATE THREAD!
@@ -3349,6 +3390,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             job_obj.multigeo = True
             job_obj.multigeo = True
             job_obj.cnc_tools.clear()
             job_obj.cnc_tools.clear()
 
 
+            job_obj.options['xmin'] = xmin
+            job_obj.options['ymin'] = ymin
+            job_obj.options['xmax'] = xmax
+            job_obj.options['ymax'] = ymax
+
             try:
             try:
                 job_obj.z_pdepth = float(self.options["z_pdepth"])
                 job_obj.z_pdepth = float(self.options["z_pdepth"])
             except ValueError:
             except ValueError:
@@ -3591,36 +3637,36 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         :return: None
         :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"]
         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'])
         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'])
         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"]
         toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
         toolchange = toolchange if toolchange else self.options["toolchange"]
         toolchange = toolchange if toolchange else self.options["toolchange"]
 
 
         offset = offset if offset else 0.0
         offset = offset if offset else 0.0
 
 
         # int or None.
         # 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"]
         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"]
         ppname_g = ppname_g if ppname_g else self.options["ppname_g"]
 
 
@@ -3803,19 +3849,19 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
         factor = Geometry.convert_units(self, units)
         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['cnctooldia'] *= factor
         # self.options['painttooldia'] *= factor
         # self.options['painttooldia'] *= factor
         # self.options['paintmargin'] *= factor
         # self.options['paintmargin'] *= factor
         # self.options['paintoverlap'] *= factor
         # self.options['paintoverlap'] *= factor
 
 
-        self.options["toolchangez"] *= factor
+        self.options["toolchangez"] = float(self.options["toolchangez"]) * factor
 
 
         if self.app.defaults["geometry_toolchangexy"] == '':
         if self.app.defaults["geometry_toolchangexy"] == '':
             self.options['toolchangexy'] = "0.0, 0.0"
             self.options['toolchangexy'] = "0.0, 0.0"
@@ -3830,7 +3876,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
             self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
 
 
         if self.options['startz'] is not None:
         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',
         param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
                       'endz', 'toolchangez']
                       'endz', 'toolchangez']
@@ -4015,7 +4061,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
 
         CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
         CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
                         feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia,
                         feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia,
-                        spindlespeed=spindlespeed, steps_per_circle=self.app.defaults["cncjob_steps_per_circle"])
+                        spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"]))
 
 
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
@@ -4212,7 +4258,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         self.to_form()
         self.to_form()
 
 
         # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
         # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
-        self.ui.cncplot_method_combo.set_value('all')
+        self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"])
 
 
         self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
         self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
         self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
         self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
@@ -4263,13 +4309,16 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
                        "G-Code Files (*.g-code);;All Files (*.*)"
                        "G-Code Files (*.g-code);;All Files (*.*)"
 
 
         try:
         try:
-            filename = str(QtWidgets.QFileDialog.getSaveFileName(
+            dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
+            filename, _ = QtWidgets.QFileDialog.getSaveFileName(
                 caption="Export Machine Code ...",
                 caption="Export Machine Code ...",
-                directory=self.app.get_last_save_folder() + '/' + name,
+                directory=dir_file_to_save,
                 filter=_filter_
                 filter=_filter_
-            )[0])
+            )
         except TypeError:
         except TypeError:
-            filename = str(QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)[0])
+            filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)
+
+        filename = str(filename)
 
 
         if filename == '':
         if filename == '':
             self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...")
             self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...")
@@ -4301,9 +4350,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         self.app.ui.code_editor.clear()
         self.app.ui.code_editor.clear()
 
 
         # then append the text from GCode to the text editor
         # then append the text from GCode to the text editor
-        for line in self.app.gcode_edited:
-            proc_line = str(line).strip('\n')
-            self.app.ui.code_editor.append(proc_line)
+        try:
+            for line in self.app.gcode_edited:
+                proc_line = str(line).strip('\n')
+                self.app.ui.code_editor.append(proc_line)
+        except Exception as e:
+            log.debug('FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e))
+            self.app.inform.emit('[ERROR]FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e))
+            return
 
 
         self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
         self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
 
 
@@ -4472,6 +4526,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         elif to_file is False:
         elif to_file is False:
             # Just for adding it to the recent files list.
             # Just for adding it to the recent files list.
             self.app.file_opened.emit("cncjob", filename)
             self.app.file_opened.emit("cncjob", filename)
+            self.app.file_saved.emit("cncjob", filename)
 
 
             self.app.inform.emit("[success] Saved to: " + filename)
             self.app.inform.emit("[success] Saved to: " + filename)
         else:
         else:
@@ -4549,7 +4604,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
 
 
         try:
         try:
             if self.multitool is False: # single tool usage
             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:
             else:
                 # multiple tools usage
                 # multiple tools usage
                 for tooluid_key in self.cnc_tools:
                 for tooluid_key in self.cnc_tools:
@@ -4564,7 +4619,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
     def convert_units(self, units):
     def convert_units(self, units):
         factor = CNCjob.convert_units(self, units)
         factor = CNCjob.convert_units(self, units)
         FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_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',
         param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
                       'endz', 'toolchangez']
                       'endz', 'toolchangez']

+ 3 - 0
FlatCAMTool.py

@@ -83,6 +83,9 @@ class FlatCAMTool(QtWidgets.QWidget):
         # Put ourself in the GUI
         # Put ourself in the GUI
         self.app.ui.tool_scroll_area.setWidget(self)
         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
         # Switch notebook to tool page
         self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
         self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
 
 

+ 167 - 5
GUIElements.py

@@ -1,14 +1,18 @@
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5 import QtGui, QtCore, QtWidgets
-from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
+from PyQt5.QtWidgets import QTextEdit, QCompleter, QAction
+from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor
 
 
 from copy import copy
 from copy import copy
 import re
 import re
 import logging
 import logging
+import html
 
 
 log = logging.getLogger('base')
 log = logging.getLogger('base')
 
 
 EDIT_SIZE_HINT = 70
 EDIT_SIZE_HINT = 70
 
 
+
 class RadioSet(QtWidgets.QWidget):
 class RadioSet(QtWidgets.QWidget):
     activated_custom = QtCore.pyqtSignal()
     activated_custom = QtCore.pyqtSignal()
 
 
@@ -229,7 +233,6 @@ class FloatEntry(QtWidgets.QLineEdit):
         else:
         else:
             self.setText("")
             self.setText("")
 
 
-
     def sizeHint(self):
     def sizeHint(self):
         default_hint_size = super(FloatEntry, self).sizeHint()
         default_hint_size = super(FloatEntry, self).sizeHint()
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
@@ -347,7 +350,7 @@ class FCEntry2(FCEntry):
         self.readyToEdit = True
         self.readyToEdit = True
 
 
     def set_value(self, val):
     def set_value(self, val):
-        self.setText('%.5f' % float(val))
+        self.setText('%.4f' % float(val))
 
 
 
 
 class EvalEntry(QtWidgets.QLineEdit):
 class EvalEntry(QtWidgets.QLineEdit):
@@ -470,6 +473,7 @@ class FCTextAreaRich(QtWidgets.QTextEdit):
         default_hint_size = super(FCTextAreaRich, self).sizeHint()
         default_hint_size = super(FCTextAreaRich, self).sizeHint()
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
         return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
 
 
+
 class FCComboBox(QtWidgets.QComboBox):
 class FCComboBox(QtWidgets.QComboBox):
     def __init__(self, parent=None):
     def __init__(self, parent=None):
         super(FCComboBox, self).__init__(parent)
         super(FCComboBox, self).__init__(parent)
@@ -490,6 +494,10 @@ class FCInputDialog(QtWidgets.QInputDialog):
         super(FCInputDialog, self).__init__(parent)
         super(FCInputDialog, self).__init__(parent)
         self.allow_empty = ok
         self.allow_empty = ok
         self.empty_val = val
         self.empty_val = val
+
+        self.val = 0.0
+        self.ok = ''
+
         if title is None:
         if title is None:
             self.title = 'title'
             self.title = 'title'
         else:
         else:
@@ -511,9 +519,8 @@ class FCInputDialog(QtWidgets.QInputDialog):
         else:
         else:
             self.decimals = decimals
             self.decimals = decimals
 
 
-
     def get_value(self):
     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)
                                                       max=self.max, decimals=self.decimals)
         return [self.val, self.ok]
         return [self.val, self.ok]
 
 
@@ -1218,3 +1225,158 @@ class Dialog_box(QtWidgets.QWidget):
 
 
         self.location, self.ok = dialog_box.getText(self, title, label)
         self.location, self.ok = dialog_box.getText(self, title, label)
 
 
+
+class _BrowserTextEdit(QTextEdit):
+
+    def __init__(self, version):
+        QTextEdit.__init__(self)
+        self.menu = None
+        self.version = version
+
+    def contextMenuEvent(self, event):
+        self.menu = self.createStandardContextMenu(event.pos())
+        clear_action = QAction("Clear", self)
+        clear_action.setShortcut(QKeySequence(Qt.Key_Delete))   # it's not working, the shortcut
+        self.menu.addAction(clear_action)
+        clear_action.triggered.connect(self.clear)
+        self.menu.exec_(event.globalPos())
+
+
+    def clear(self):
+        QTextEdit.clear(self)
+        text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version
+        text = html.escape(text)
+        text = text.replace('\n', '<br/>')
+        self.moveCursor(QTextCursor.End)
+        self.insertHtml(text)
+
+
+class _ExpandableTextEdit(QTextEdit):
+    """
+    Class implements edit line, which expands themselves automatically
+    """
+
+    historyNext = pyqtSignal()
+    historyPrev = pyqtSignal()
+
+    def __init__(self, termwidget, *args):
+        QTextEdit.__init__(self, *args)
+        self.setStyleSheet("font: 9pt \"Courier\";")
+        self._fittedHeight = 1
+        self.textChanged.connect(self._fit_to_document)
+        self._fit_to_document()
+        self._termWidget = termwidget
+
+        self.completer = MyCompleter()
+
+        self.model = QtCore.QStringListModel()
+        self.completer.setModel(self.model)
+        self.set_model_data(keyword_list=[])
+        self.completer.insertText.connect(self.insertCompletion)
+
+    def set_model_data(self, keyword_list):
+        self.model.setStringList(keyword_list)
+
+    def insertCompletion(self, completion):
+        tc = self.textCursor()
+        extra = (len(completion) - len(self.completer.completionPrefix()))
+        tc.movePosition(QTextCursor.Left)
+        tc.movePosition(QTextCursor.EndOfWord)
+        tc.insertText(completion[-extra:])
+        self.setTextCursor(tc)
+        self.completer.popup().hide()
+
+    def focusInEvent(self, event):
+        if self.completer:
+            self.completer.setWidget(self)
+        QTextEdit.focusInEvent(self, event)
+
+    def keyPressEvent(self, event):
+        """
+        Catch keyboard events. Process Enter, Up, Down
+        """
+        if event.matches(QKeySequence.InsertParagraphSeparator):
+            text = self.toPlainText()
+            if self._termWidget.is_command_complete(text):
+                self._termWidget.exec_current_command()
+                return
+        elif event.matches(QKeySequence.MoveToNextLine):
+            text = self.toPlainText()
+            cursor_pos = self.textCursor().position()
+            textBeforeEnd = text[cursor_pos:]
+
+            if len(textBeforeEnd.split('\n')) <= 1:
+                self.historyNext.emit()
+                return
+        elif event.matches(QKeySequence.MoveToPreviousLine):
+            text = self.toPlainText()
+            cursor_pos = self.textCursor().position()
+            text_before_start = text[:cursor_pos]
+            # lineCount = len(textBeforeStart.splitlines())
+            line_count = len(text_before_start.split('\n'))
+            if len(text_before_start) > 0 and \
+                    (text_before_start[-1] == '\n' or text_before_start[-1] == '\r'):
+                line_count += 1
+            if line_count <= 1:
+                self.historyPrev.emit()
+                return
+        elif event.matches(QKeySequence.MoveToNextPage) or \
+                event.matches(QKeySequence.MoveToPreviousPage):
+            return self._termWidget.browser().keyPressEvent(event)
+
+        tc = self.textCursor()
+        if event.key() == Qt.Key_Tab and self.completer.popup().isVisible():
+            self.completer.insertText.emit(self.completer.getSelected())
+            self.completer.setCompletionMode(QCompleter.PopupCompletion)
+            return
+
+        QTextEdit.keyPressEvent(self, event)
+        tc.select(QTextCursor.WordUnderCursor)
+        cr = self.cursorRect()
+
+        if len(tc.selectedText()) > 0:
+            self.completer.setCompletionPrefix(tc.selectedText())
+            popup = self.completer.popup()
+            popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
+
+            cr.setWidth(self.completer.popup().sizeHintForColumn(0)
+                        + self.completer.popup().verticalScrollBar().sizeHint().width())
+            self.completer.complete(cr)
+        else:
+            self.completer.popup().hide()
+
+    def sizeHint(self):
+        """
+        QWidget sizeHint impelemtation
+        """
+        hint = QTextEdit.sizeHint(self)
+        hint.setHeight(self._fittedHeight)
+        return hint
+
+    def _fit_to_document(self):
+        """
+        Update widget height to fit all text
+        """
+        documentsize = self.document().size().toSize()
+        self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height())
+        self.setMaximumHeight(self._fittedHeight)
+        self.updateGeometry()
+
+    def insertFromMimeData(self, mime_data):
+        # Paste only plain text.
+        self.insertPlainText(mime_data.text())
+
+
+class MyCompleter(QCompleter):
+    insertText = pyqtSignal(str)
+
+    def __init__(self, parent=None):
+        QCompleter.__init__(self)
+        self.setCompletionMode(QCompleter.PopupCompletion)
+        self.highlighted.connect(self.setHighlighted)
+
+    def setHighlighted(self, text):
+        self.lastSelected = text
+
+    def getSelected(self):
+        return self.lastSelected

+ 241 - 238
ObjectCollection.py

@@ -12,7 +12,7 @@ import inspect  # TODO: Remove
 import FlatCAMApp
 import FlatCAMApp
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5 import QtGui, QtCore, QtWidgets
 from PyQt5.QtCore import Qt
 from PyQt5.QtCore import Qt
-import webbrowser
+# import webbrowser
 
 
 
 
 class KeySensitiveListView(QtWidgets.QTreeView):
 class KeySensitiveListView(QtWidgets.QTreeView):
@@ -36,7 +36,7 @@ class KeySensitiveListView(QtWidgets.QTreeView):
     keyPressed = QtCore.pyqtSignal(int)
     keyPressed = QtCore.pyqtSignal(int)
 
 
     def keyPressEvent(self, event):
     def keyPressEvent(self, event):
-        super(KeySensitiveListView, self).keyPressEvent(event)
+        # super(KeySensitiveListView, self).keyPressEvent(event)
         self.keyPressed.emit(event.key())
         self.keyPressed.emit(event.key())
 
 
     def dragEnterEvent(self, event):
     def dragEnterEvent(self, event):
@@ -228,6 +228,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         # tasks know that they have to wait until available.
         # tasks know that they have to wait until available.
         self.promises = set()
         self.promises = set()
 
 
+        self.app = app
+
         ### View
         ### View
         self.view = KeySensitiveListView(app)
         self.view = KeySensitiveListView(app)
         self.view.setModel(self)
         self.view.setModel(self)
@@ -247,7 +249,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         ## GUI Events
         ## GUI Events
         self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
         self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
         self.view.activated.connect(self.on_item_activated)
         self.view.activated.connect(self.on_item_activated)
-        self.view.keyPressed.connect(self.on_key)
+        # self.view.keyPressed.connect(self.on_key)
+        self.view.keyPressed.connect(self.app.ui.keyPressEvent)
         self.view.clicked.connect(self.on_mouse_down)
         self.view.clicked.connect(self.on_mouse_down)
         self.view.customContextMenuRequested.connect(self.on_menu_request)
         self.view.customContextMenuRequested.connect(self.on_menu_request)
 
 
@@ -260,238 +263,238 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     def has_promises(self):
     def has_promises(self):
         return len(self.promises) > 0
         return len(self.promises) > 0
 
 
-    def on_key(self, key):
-        modifiers = QtWidgets.QApplication.keyboardModifiers()
-        active = self.get_active()
-        selected = self.get_selected()
-
-        if modifiers == QtCore.Qt.ControlModifier:
-            if key == QtCore.Qt.Key_A:
-                self.app.on_selectall()
-
-            if key == QtCore.Qt.Key_C:
-                self.app.on_copy_object()
-
-            if key == QtCore.Qt.Key_E:
-                self.app.on_fileopenexcellon()
-
-            if key == QtCore.Qt.Key_G:
-                self.app.on_fileopengerber()
-
-            if key == QtCore.Qt.Key_N:
-                self.app.on_file_new_click()
-
-            if key == QtCore.Qt.Key_M:
-                self.app.measurement_tool.run()
-            if key == QtCore.Qt.Key_O:
-                self.app.on_file_openproject()
-
-            if key == QtCore.Qt.Key_S:
-                self.app.on_file_saveproject()
-
-            # Toggle Plot Area
-            if key == QtCore.Qt.Key_F10:
-                self.app.on_toggle_plotarea()
-
-            return
-        elif modifiers == QtCore.Qt.ShiftModifier:
-
-            # Copy Object Name
-            # Copy Object Name
-            if key == QtCore.Qt.Key_C:
-                self.app.on_copy_name()
-
-            # Toggle axis
-            if key == QtCore.Qt.Key_G:
-                if self.toggle_axis is False:
-                    self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
-                    self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
-                    self.app.plotcanvas.redraw()
-                    self.app.toggle_axis = True
-                else:
-                    self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
-
-                    self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
-                    self.appplotcanvas.redraw()
-                    self.app.toggle_axis = False
-
-            # Open Preferences Window
-            if key == QtCore.Qt.Key_P:
-                self.app.on_preferences()
-                return
-
-            # Rotate Object by 90 degree CCW
-            if key == QtCore.Qt.Key_R:
-                self.app.on_rotate(silent=True, preset=-90)
-                return
-
-            # Run a Script
-            if key == QtCore.Qt.Key_S:
-                self.app.on_filerunscript()
-                return
-
-            # Toggle Workspace
-            if key == QtCore.Qt.Key_W:
-                self.app.on_workspace_menu()
-                return
-
-            # Skew on X axis
-            if key == QtCore.Qt.Key_X:
-                self.app.on_skewx()
-                return
-
-            # Skew on Y axis
-            if key == QtCore.Qt.Key_Y:
-                self.app.on_skewy()
-                return
-
-        elif modifiers == QtCore.Qt.AltModifier:
-            # Eanble all plots
-            if key == Qt.Key_1:
-                self.app.enable_all_plots()
-
-            # Disable all plots
-            if key == Qt.Key_2:
-                self.app.disable_all_plots()
-
-            # Disable all other plots
-            if key == Qt.Key_3:
-                self.app.disable_other_plots()
-
-            # 2-Sided PCB Tool
-            if key == QtCore.Qt.Key_D:
-                self.app.dblsidedtool.run()
-                return
-
-            # Non-Copper Clear Tool
-            if key == QtCore.Qt.Key_N:
-                self.app.ncclear_tool.run()
-                return
-
-            # Transformation Tool
-            if key == QtCore.Qt.Key_R:
-                self.app.transform_tool.run()
-                return
-
-            # Cutout Tool
-            if key == QtCore.Qt.Key_U:
-                self.app.cutout_tool.run()
-                return
-
-        else:
-            # Open Manual
-            if key == QtCore.Qt.Key_F1:
-                webbrowser.open(self.app.manual_url)
-
-            # Open Video Help
-            if key == QtCore.Qt.Key_F2:
-                webbrowser.open(self.app.video_url)
-
-            # Switch to Project Tab
-            if key == QtCore.Qt.Key_1:
-                self.app.on_select_tab('project')
-
-            # Switch to Selected Tab
-            if key == QtCore.Qt.Key_2:
-                self.app.on_select_tab('selected')
-
-            # Switch to Tool Tab
-            if key == QtCore.Qt.Key_3:
-                self.app.on_select_tab('tool')
-
-            # Delete
-            if key == QtCore.Qt.Key_Delete and active:
-                # Delete via the application to
-                # ensure cleanup of the GUI
-                active.app.on_delete()
-
-            # Space = Toggle Active/Inactive
-            if key == QtCore.Qt.Key_Space:
-                for select in selected:
-                    select.ui.plot_cb.toggle()
-                self.app.delete_selection_shape()
-
-            # Copy Object Name
-            if key == QtCore.Qt.Key_E:
-                self.app.object2editor()
-
-            # Grid toggle
-            if key == QtCore.Qt.Key_G:
-                self.app.geo_editor.grid_snap_btn.trigger()
-
-            # Jump to coords
-            if key == QtCore.Qt.Key_J:
-                self.app.on_jump_to()
-
-            # New Excellon
-            if key == QtCore.Qt.Key_L:
-                self.app.new_excellon_object()
-
-            # Move tool toggle
-            if key == QtCore.Qt.Key_M:
-                self.app.move_tool.toggle()
-
-            # New Geometry
-            if key == QtCore.Qt.Key_N:
-                self.app.on_new_geometry()
-
-            # Set Origin
-            if key == QtCore.Qt.Key_O:
-                self.app.on_set_origin()
-                return
-
-            # Set Origin
-            if key == QtCore.Qt.Key_P:
-                self.app.properties_tool.run()
-                return
-
-            # Change Units
-            if key == QtCore.Qt.Key_Q:
-                if self.app.options["units"] == 'MM':
-                    self.app.general_options_form.general_app_group.units_radio.set_value("IN")
-                else:
-                    self.app.general_options_form.general_app_group.units_radio.set_value("MM")
-                self.app.on_toggle_units()
-
-            # Rotate Object by 90 degree CW
-            if key == QtCore.Qt.Key_R:
-                self.app.on_rotate(silent=True, preset=90)
-
-            # Shell toggle
-            if key == QtCore.Qt.Key_S:
-                self.app.on_toggle_shell()
-
-            # Transform Tool
-            if key == QtCore.Qt.Key_T:
-                self.app.transform_tool.run()
-
-            # Zoom Fit
-            if key == QtCore.Qt.Key_V:
-                self.app.on_zoom_fit(None)
-
-            # Mirror on X the selected object(s)
-            if key == QtCore.Qt.Key_X:
-                self.app.on_flipx()
-
-            # Mirror on Y the selected object(s)
-            if key == QtCore.Qt.Key_Y:
-                self.app.on_flipy()
-
-            # Zoom In
-            if key == QtCore.Qt.Key_Equal:
-                self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse)
-
-            # Zoom Out
-            if key == QtCore.Qt.Key_Minus:
-                self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse)
-
-            # Show shortcut list
-            if key == QtCore.Qt.Key_Ampersand:
-                self.app.on_shortcut_list()
-
-            if key == QtCore.Qt.Key_QuoteLeft:
-                self.app.on_shortcut_list()
-            return
+    # def on_key(self, key):
+    #     modifiers = QtWidgets.QApplication.keyboardModifiers()
+    #     active = self.get_active()
+    #     selected = self.get_selected()
+    #
+    #     if modifiers == QtCore.Qt.ControlModifier:
+    #         if key == QtCore.Qt.Key_A:
+    #             self.app.on_selectall()
+    #
+    #         if key == QtCore.Qt.Key_C:
+    #             self.app.on_copy_object()
+    #
+    #         if key == QtCore.Qt.Key_E:
+    #             self.app.on_fileopenexcellon()
+    #
+    #         if key == QtCore.Qt.Key_G:
+    #             self.app.on_fileopengerber()
+    #
+    #         if key == QtCore.Qt.Key_N:
+    #             self.app.on_file_new_click()
+    #
+    #         if key == QtCore.Qt.Key_M:
+    #             self.app.measurement_tool.run()
+    #         if key == QtCore.Qt.Key_O:
+    #             self.app.on_file_openproject()
+    #
+    #         if key == QtCore.Qt.Key_S:
+    #             self.app.on_file_saveproject()
+    #
+    #         # Toggle Plot Area
+    #         if key == QtCore.Qt.Key_F10:
+    #             self.app.on_toggle_plotarea()
+    #
+    #         return
+    #     elif modifiers == QtCore.Qt.ShiftModifier:
+    #
+    #         # Copy Object Name
+    #         # Copy Object Name
+    #         if key == QtCore.Qt.Key_C:
+    #             self.app.on_copy_name()
+    #
+    #         # Toggle axis
+    #         if key == QtCore.Qt.Key_G:
+    #             if self.toggle_axis is False:
+    #                 self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
+    #                 self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
+    #                 self.app.plotcanvas.redraw()
+    #                 self.app.toggle_axis = True
+    #             else:
+    #                 self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
+    #
+    #                 self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
+    #                 self.appplotcanvas.redraw()
+    #                 self.app.toggle_axis = False
+    #
+    #         # Open Preferences Window
+    #         if key == QtCore.Qt.Key_P:
+    #             self.app.on_preferences()
+    #             return
+    #
+    #         # Rotate Object by 90 degree CCW
+    #         if key == QtCore.Qt.Key_R:
+    #             self.app.on_rotate(silent=True, preset=-90)
+    #             return
+    #
+    #         # Run a Script
+    #         if key == QtCore.Qt.Key_S:
+    #             self.app.on_filerunscript()
+    #             return
+    #
+    #         # Toggle Workspace
+    #         if key == QtCore.Qt.Key_W:
+    #             self.app.on_workspace_menu()
+    #             return
+    #
+    #         # Skew on X axis
+    #         if key == QtCore.Qt.Key_X:
+    #             self.app.on_skewx()
+    #             return
+    #
+    #         # Skew on Y axis
+    #         if key == QtCore.Qt.Key_Y:
+    #             self.app.on_skewy()
+    #             return
+    #
+    #     elif modifiers == QtCore.Qt.AltModifier:
+    #         # Eanble all plots
+    #         if key == Qt.Key_1:
+    #             self.app.enable_all_plots()
+    #
+    #         # Disable all plots
+    #         if key == Qt.Key_2:
+    #             self.app.disable_all_plots()
+    #
+    #         # Disable all other plots
+    #         if key == Qt.Key_3:
+    #             self.app.disable_other_plots()
+    #
+    #         # 2-Sided PCB Tool
+    #         if key == QtCore.Qt.Key_D:
+    #             self.app.dblsidedtool.run()
+    #             return
+    #
+    #         # Non-Copper Clear Tool
+    #         if key == QtCore.Qt.Key_N:
+    #             self.app.ncclear_tool.run()
+    #             return
+    #
+    #         # Transformation Tool
+    #         if key == QtCore.Qt.Key_R:
+    #             self.app.transform_tool.run()
+    #             return
+    #
+    #         # Cutout Tool
+    #         if key == QtCore.Qt.Key_U:
+    #             self.app.cutout_tool.run()
+    #             return
+    #
+    #     else:
+    #         # Open Manual
+    #         if key == QtCore.Qt.Key_F1:
+    #             webbrowser.open(self.app.manual_url)
+    #
+    #         # Open Video Help
+    #         if key == QtCore.Qt.Key_F2:
+    #             webbrowser.open(self.app.video_url)
+    #
+    #         # Switch to Project Tab
+    #         if key == QtCore.Qt.Key_1:
+    #             self.app.on_select_tab('project')
+    #
+    #         # Switch to Selected Tab
+    #         if key == QtCore.Qt.Key_2:
+    #             self.app.on_select_tab('selected')
+    #
+    #         # Switch to Tool Tab
+    #         if key == QtCore.Qt.Key_3:
+    #             self.app.on_select_tab('tool')
+    #
+    #         # Delete
+    #         if key == QtCore.Qt.Key_Delete and active:
+    #             # Delete via the application to
+    #             # ensure cleanup of the GUI
+    #             active.app.on_delete()
+    #
+    #         # Space = Toggle Active/Inactive
+    #         if key == QtCore.Qt.Key_Space:
+    #             for select in selected:
+    #                 select.ui.plot_cb.toggle()
+    #             self.app.delete_selection_shape()
+    #
+    #         # Copy Object Name
+    #         if key == QtCore.Qt.Key_E:
+    #             self.app.object2editor()
+    #
+    #         # Grid toggle
+    #         if key == QtCore.Qt.Key_G:
+    #             self.app.ui.grid_snap_btn.trigger()
+    #
+    #         # Jump to coords
+    #         if key == QtCore.Qt.Key_J:
+    #             self.app.on_jump_to()
+    #
+    #         # New Excellon
+    #         if key == QtCore.Qt.Key_L:
+    #             self.app.new_excellon_object()
+    #
+    #         # Move tool toggle
+    #         if key == QtCore.Qt.Key_M:
+    #             self.app.move_tool.toggle()
+    #
+    #         # New Geometry
+    #         if key == QtCore.Qt.Key_N:
+    #             self.app.on_new_geometry()
+    #
+    #         # Set Origin
+    #         if key == QtCore.Qt.Key_O:
+    #             self.app.on_set_origin()
+    #             return
+    #
+    #         # Set Origin
+    #         if key == QtCore.Qt.Key_P:
+    #             self.app.properties_tool.run()
+    #             return
+    #
+    #         # Change Units
+    #         if key == QtCore.Qt.Key_Q:
+    #             if self.app.options["units"] == 'MM':
+    #                 self.app.general_options_form.general_app_group.units_radio.set_value("IN")
+    #             else:
+    #                 self.app.general_options_form.general_app_group.units_radio.set_value("MM")
+    #             self.app.on_toggle_units()
+    #
+    #         # Rotate Object by 90 degree CW
+    #         if key == QtCore.Qt.Key_R:
+    #             self.app.on_rotate(silent=True, preset=90)
+    #
+    #         # Shell toggle
+    #         if key == QtCore.Qt.Key_S:
+    #             self.app.on_toggle_shell()
+    #
+    #         # Transform Tool
+    #         if key == QtCore.Qt.Key_T:
+    #             self.app.transform_tool.run()
+    #
+    #         # Zoom Fit
+    #         if key == QtCore.Qt.Key_V:
+    #             self.app.on_zoom_fit(None)
+    #
+    #         # Mirror on X the selected object(s)
+    #         if key == QtCore.Qt.Key_X:
+    #             self.app.on_flipx()
+    #
+    #         # Mirror on Y the selected object(s)
+    #         if key == QtCore.Qt.Key_Y:
+    #             self.app.on_flipy()
+    #
+    #         # Zoom In
+    #         if key == QtCore.Qt.Key_Equal:
+    #             self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse)
+    #
+    #         # Zoom Out
+    #         if key == QtCore.Qt.Key_Minus:
+    #             self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse)
+    #
+    #         # Show shortcut list
+    #         if key == QtCore.Qt.Key_Ampersand:
+    #             self.app.on_shortcut_list()
+    #
+    #         if key == QtCore.Qt.Key_QuoteLeft:
+    #             self.app.on_shortcut_list()
+    #         return
 
 
     def on_mouse_down(self, event):
     def on_mouse_down(self, event):
         FlatCAMApp.App.log.debug("Mouse button pressed on list")
         FlatCAMApp.App.log.debug("Mouse button pressed on list")
@@ -681,7 +684,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         :rtype: list
         :rtype: list
         """
         """
 
 
-        FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
+        # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
         return [x.options['name'] for x in self.get_list()]
         return [x.options['name'] for x in self.get_list()]
 
 
     def get_bounds(self):
     def get_bounds(self):
@@ -875,8 +878,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
             self.set_inactive(name)
             self.set_inactive(name)
 
 
     def on_list_selection_change(self, current, previous):
     def on_list_selection_change(self, current, previous):
-        FlatCAMApp.App.log.debug("on_list_selection_change()")
-        FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
+        # FlatCAMApp.App.log.debug("on_list_selection_change()")
+        # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
 
 
         try:
         try:
             obj = current.indexes()[0].internalPointer().obj
             obj = current.indexes()[0].internalPointer().obj

+ 11 - 4
ObjectUI.py

@@ -611,7 +611,8 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_box.addLayout(gcode_box)
         self.tools_box.addLayout(gcode_box)
 
 
         # temporary action until I finish the feature
         # temporary action until I finish the feature
-        self.excellon_gcode_type_radio.setEnabled(False)
+        self.excellon_gcode_type_radio.setVisible(False)
+        gcode_type_label.hide()
 
 
         self.generate_cnc_button = QtWidgets.QPushButton('Create GCode')
         self.generate_cnc_button = QtWidgets.QPushButton('Create GCode')
         self.generate_cnc_button.setToolTip(
         self.generate_cnc_button.setToolTip(
@@ -783,7 +784,8 @@ class GeometryObjectUI(ObjectUI):
             "cut and negative for 'inside' cut."
             "cut and negative for 'inside' cut."
         )
         )
         self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
         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 = QtWidgets.QLabel(" ")
         spacer_lbl.setFixedWidth(80)
         spacer_lbl.setFixedWidth(80)
 
 
@@ -1129,9 +1131,14 @@ class CNCObjectUI(ObjectUI):
             {"label": "Cut", "value": "cut"}
             {"label": "Cut", "value": "cut"}
         ], stretch=False)
         ], stretch=False)
 
 
-        f_lay = QtWidgets.QFormLayout()
+        f_lay = QtWidgets.QGridLayout()
+        f_lay.setColumnStretch(1, 1)
+        f_lay.setColumnStretch(2, 1)
+
         self.custom_box.addLayout(f_lay)
         self.custom_box.addLayout(f_lay)
-        f_lay.addRow(self.cncplot_method_label, self.cncplot_method_combo)
+        f_lay.addWidget(self.cncplot_method_label, 0, 0)
+        f_lay.addWidget(self.cncplot_method_combo, 0, 1)
+        f_lay.addWidget(QtWidgets.QLabel(''), 0, 2)
 
 
         e1_lbl = QtWidgets.QLabel('')
         e1_lbl = QtWidgets.QLabel('')
         self.custom_box.addWidget(e1_lbl)
         self.custom_box.addWidget(e1_lbl)

+ 55 - 0
README.md

@@ -9,9 +9,64 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+9.02.2019
+
+- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory
+- added into Preferences the Calculator Tools
+- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before)
+- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor
+- changed the messages from status bar on new object creation/selection
+- in Geometry Editor fixed the handler for the Rotate shortcut key ('R')
+
+8.02.2019
+
+- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible
+- changed the menu entry Toggle Grid name to Toggle Grid Snap
+- 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
+- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value.
+
+7.02.2019
+
+- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object
+- commented some debug messages
+- imported speedups for shapely
+- added a disable menu entry in the canvas contextual menu
+- small changes in Tools layout
+- added some new icons in the help menu and reorganized this menu
+- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section
+- changed the Shortcut list shortcut key to F3
+- moved some graphical classes out of Tool Shell to GUIElements.py where they belong
+- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed
+- in Move Tool I've added the type of object that was moved in the status bar message
+- color coded the status bar bullet to blue for selection
+- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs.
+
 6.02.2019
 6.02.2019
 
 
 - fixed the units calculators crash FlatCAM when using comma as decimal separator
 - fixed the units calculators crash FlatCAM when using comma as decimal separator
+- done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it
+- fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation
+- fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. 
+- fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it.
+- fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None
+- fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots.
+- changed the messages for Units Conversion
+- all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent()
+- renamed the theme to layout because it is really a layout change
+- added plot kind for CNC Job in the App Preferences
+- combined the geocutout and cutout_any TCL commands - work in progress
+- added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) 
+- fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences
+- fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects)
 
 
 5.02.3019
 5.02.3019
 
 

+ 287 - 209
camlib.py

@@ -31,6 +31,7 @@ from shapely.wkt import loads as sloads
 from shapely.wkt import dumps as sdumps
 from shapely.wkt import dumps as sdumps
 from shapely.geometry.base import BaseGeometry
 from shapely.geometry.base import BaseGeometry
 from shapely.geometry import shape
 from shapely.geometry import shape
+from shapely import speedups
 
 
 from collections import Iterable
 from collections import Iterable
 
 
@@ -101,7 +102,7 @@ class Geometry(object):
         self.geo_steps_per_circle = geo_steps_per_circle
         self.geo_steps_per_circle = geo_steps_per_circle
 
 
         if geo_steps_per_circle is None:
         if geo_steps_per_circle is None:
-            geo_steps_per_circle = Geometry.defaults["geo_steps_per_circle"]
+            geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"])
         self.geo_steps_per_circle = geo_steps_per_circle
         self.geo_steps_per_circle = geo_steps_per_circle
 
 
     def make_index(self):
     def make_index(self):
@@ -494,7 +495,7 @@ class Geometry(object):
     #
     #
     #     return self.flat_geometry, self.flat_geometry_rtree
     #     return self.flat_geometry, self.flat_geometry_rtree
 
 
-    def isolation_geometry(self, offset, iso_type=2):
+    def isolation_geometry(self, offset, iso_type=2, corner=None):
         """
         """
         Creates contours around geometry at a given
         Creates contours around geometry at a given
         offset distance.
         offset distance.
@@ -503,6 +504,7 @@ class Geometry(object):
         :type offset: float
         :type offset: float
         :param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
         :param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
         :type integer
         :type integer
+        :param corner: type of corner for the isolation: 0 = round; 1 = square; 2= beveled (line that connects the ends)
         :return: The buffered geometry.
         :return: The buffered geometry.
         :rtype: Shapely.MultiPolygon or Shapely.Polygon
         :rtype: Shapely.MultiPolygon or Shapely.Polygon
         """
         """
@@ -537,7 +539,11 @@ class Geometry(object):
         if offset == 0:
         if offset == 0:
             geo_iso = self.solid_geometry
             geo_iso = self.solid_geometry
         else:
         else:
-            geo_iso = self.solid_geometry.buffer(offset, int(self.geo_steps_per_circle / 4))
+            if corner is None:
+                geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4))
+            else:
+                geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner)
+
         # end of replaced block
         # end of replaced block
 
 
         if iso_type == 2:
         if iso_type == 2:
@@ -790,7 +796,7 @@ class Geometry(object):
 
 
         # Can only result in a Polygon or MultiPolygon
         # Can only result in a Polygon or MultiPolygon
         # NOTE: The resulting polygon can be "empty".
         # NOTE: The resulting polygon can be "empty".
-        current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle / 4))
+        current = polygon.buffer((-tooldia / 1.999999), int(int(steps_per_circle) / 4))
         if current.area == 0:
         if current.area == 0:
             # Otherwise, trying to to insert current.exterior == None
             # Otherwise, trying to to insert current.exterior == None
             # into the FlatCAMStorage will fail.
             # into the FlatCAMStorage will fail.
@@ -813,7 +819,7 @@ class Geometry(object):
         while True:
         while True:
 
 
             # Can only result in a Polygon or MultiPolygon
             # Can only result in a Polygon or MultiPolygon
-            current = current.buffer(-tooldia * (1 - overlap), int(steps_per_circle / 4))
+            current = current.buffer(-tooldia * (1 - overlap), int(int(steps_per_circle) / 4))
             if current.area > 0:
             if current.area > 0:
 
 
                 # current can be a MultiPolygon
                 # current can be a MultiPolygon
@@ -829,13 +835,13 @@ class Geometry(object):
                     for i in current.interiors:
                     for i in current.interiors:
                         geoms.insert(i)
                         geoms.insert(i)
             else:
             else:
-                print("Current Area is zero")
+                log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero")
                 break
                 break
 
 
         # Optimization: Reduce lifts
         # Optimization: Reduce lifts
         if connect:
         if connect:
             # log.debug("Reducing tool lifts...")
             # log.debug("Reducing tool lifts...")
-            geoms = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle)
+            geoms = Geometry.paint_connect(geoms, polygon, tooldia, int(steps_per_circle))
 
 
         return geoms
         return geoms
 
 
@@ -1873,11 +1879,11 @@ class Gerber (Geometry):
 
 
         # How to discretize a circle.
         # How to discretize a circle.
         if steps_per_circle is None:
         if steps_per_circle is None:
-            steps_per_circle = Gerber.defaults['steps_per_circle']
-        self.steps_per_circle = steps_per_circle
+            steps_per_circle = int(Gerber.defaults['steps_per_circle'])
+        self.steps_per_circle = int(steps_per_circle)
 
 
         # Initialize parent
         # Initialize parent
-        Geometry.__init__(self, geo_steps_per_circle=steps_per_circle)
+        Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
 
 
         self.solid_geometry = Polygon()
         self.solid_geometry = Polygon()
 
 
@@ -3268,10 +3274,10 @@ class Excellon(Geometry):
         """
         """
 
 
         if geo_steps_per_circle is None:
         if geo_steps_per_circle is None:
-            geo_steps_per_circle = Excellon.defaults['geo_steps_per_circle']
-        self.geo_steps_per_circle = geo_steps_per_circle
+            geo_steps_per_circle = int(Excellon.defaults['geo_steps_per_circle'])
+        self.geo_steps_per_circle = int(geo_steps_per_circle)
 
 
-        Geometry.__init__(self, geo_steps_per_circle=geo_steps_per_circle)
+        Geometry.__init__(self, geo_steps_per_circle=int(geo_steps_per_circle))
 
 
         # dictionary to store tools, see above for description
         # dictionary to store tools, see above for description
         self.tools = {}
         self.tools = {}
@@ -4382,10 +4388,10 @@ class CNCjob(Geometry):
 
 
         # Used when parsing G-code arcs
         # Used when parsing G-code arcs
         if steps_per_circle is None:
         if steps_per_circle is None:
-            steps_per_circle = CNCjob.defaults["steps_per_circle"]
-        self.steps_per_circle = steps_per_circle
+            steps_per_circle = int(CNCjob.defaults["steps_per_circle"])
+        self.steps_per_circle = int(steps_per_circle)
 
 
-        Geometry.__init__(self, geo_steps_per_circle=steps_per_circle)
+        Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
 
 
         self.kind = kind
         self.kind = kind
         self.units = units
         self.units = units
@@ -4549,7 +4555,7 @@ class CNCjob(Geometry):
         elif drillz == 0:
         elif drillz == 0:
             self.app.inform.emit("[WARNING] The Cut Z parameter is zero. "
             self.app.inform.emit("[WARNING] The Cut Z parameter is zero. "
                                  "There will be no cut, skipping %s file" % exobj.options['name'])
                                  "There will be no cut, skipping %s file" % exobj.options['name'])
-            return
+            return 'fail'
         else:
         else:
             self.z_cut = drillz
             self.z_cut = drillz
 
 
@@ -4670,139 +4676,188 @@ class CNCjob(Geometry):
         if current_platform == '64bit':
         if current_platform == '64bit':
             if excellon_optimization_type == 'M':
             if excellon_optimization_type == 'M':
                 log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.")
                 log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.")
-                for tool in tools:
-                    self.tool=tool
-                    self.postdata['toolC']=exobj.tools[tool]["C"]
-
-                    ################################################
-                    # Create the data.
-                    node_list = []
-                    locations = create_data_array()
-                    tsp_size = len(locations)
-                    num_routes = 1  # The number of routes, which is 1 in the TSP.
-                    # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
-                    depot = 0
-                    # Create routing model.
-                    if tsp_size > 0:
-                        routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
-                        search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
-                        search_parameters.local_search_metaheuristic = (
-                            routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
-
-                        # Set search time limit in milliseconds.
-                        if float(self.app.defaults["excellon_search_time"]) != 0:
-                            search_parameters.time_limit_ms = int(
-                                float(self.app.defaults["excellon_search_time"]) * 1000)
-                        else:
-                            search_parameters.time_limit_ms = 3000
-
-                        # Callback to the distance function. The callback takes two
-                        # arguments (the from and to node indices) and returns the distance between them.
-                        dist_between_locations = CreateDistanceCallback()
-                        dist_callback = dist_between_locations.Distance
-                        routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
-
-                        # Solve, returns a solution if any.
-                        assignment = routing.SolveWithParameters(search_parameters)
-
-                        if assignment:
-                            # Solution cost.
-                            log.info("Total distance: " + str(assignment.ObjectiveValue()))
-
-                            # Inspect solution.
-                            # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
-                            route_number = 0
-                            node = routing.Start(route_number)
-                            start_node = node
-
-                            while not routing.IsEnd(node):
-                                node_list.append(node)
-                                node = assignment.Value(routing.NextVar(node))
-                        else:
-                            log.warning('No solution found.')
-                    else:
-                        log.warning('Specify an instance greater than 0.')
-                    ################################################
-
-                    # Only if tool has points.
-                    if tool in points:
-                        # Tool change sequence (optional)
-                        if toolchange:
-                            gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
-                            gcode += self.doformat(p.spindle_code)  # Spindle start
-                            if self.dwell is True:
-                                gcode += self.doformat(p.dwell_code)  # Dwell time
+                if exobj.drills:
+                    for tool in tools:
+                        self.tool=tool
+                        self.postdata['toolC']=exobj.tools[tool]["C"]
+
+                        ################################################
+                        # Create the data.
+                        node_list = []
+                        locations = create_data_array()
+                        tsp_size = len(locations)
+                        num_routes = 1  # The number of routes, which is 1 in the TSP.
+                        # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
+                        depot = 0
+                        # Create routing model.
+                        if tsp_size > 0:
+                            routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
+                            search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
+                            search_parameters.local_search_metaheuristic = (
+                                routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
+
+                            # Set search time limit in milliseconds.
+                            if float(self.app.defaults["excellon_search_time"]) != 0:
+                                search_parameters.time_limit_ms = int(
+                                    float(self.app.defaults["excellon_search_time"]) * 1000)
+                            else:
+                                search_parameters.time_limit_ms = 3000
+
+                            # Callback to the distance function. The callback takes two
+                            # arguments (the from and to node indices) and returns the distance between them.
+                            dist_between_locations = CreateDistanceCallback()
+                            dist_callback = dist_between_locations.Distance
+                            routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
+
+                            # Solve, returns a solution if any.
+                            assignment = routing.SolveWithParameters(search_parameters)
+
+                            if assignment:
+                                # Solution cost.
+                                log.info("Total distance: " + str(assignment.ObjectiveValue()))
+
+                                # Inspect solution.
+                                # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
+                                route_number = 0
+                                node = routing.Start(route_number)
+                                start_node = node
+
+                                while not routing.IsEnd(node):
+                                    node_list.append(node)
+                                    node = assignment.Value(routing.NextVar(node))
+                            else:
+                                log.warning('No solution found.')
                         else:
                         else:
-                            gcode += self.doformat(p.spindle_code)
-                            if self.dwell is True:
-                                gcode += self.doformat(p.dwell_code)  # Dwell time
+                            log.warning('Specify an instance greater than 0.')
+                        ################################################
+
+                        # Only if tool has points.
+                        if tool in points:
+                            # Tool change sequence (optional)
+                            if toolchange:
+                                gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
+                                gcode += self.doformat(p.spindle_code)  # Spindle start
+                                if self.dwell is True:
+                                    gcode += self.doformat(p.dwell_code)  # Dwell time
+                            else:
+                                gcode += self.doformat(p.spindle_code)
+                                if self.dwell is True:
+                                    gcode += self.doformat(p.dwell_code)  # Dwell time
+
+                            # Drillling!
+                            for k in node_list:
+                                locx = locations[k][0]
+                                locy = locations[k][1]
+
+                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                gcode += self.doformat(p.down_code, x=locx, y=locy)
+                                gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
+                                gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
+                                self.oldx = locx
+                                self.oldy = locy
+                else:
+                    log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
+                              "The loaded Excellon file has no drills ...")
+                    self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...')
+                    return 'fail'
 
 
-                        # Drillling!
-                        for k in node_list:
-                            locx = locations[k][0]
-                            locy = locations[k][1]
-
-                            gcode += self.doformat(p.rapid_code, x=locx, y=locy)
-                            gcode += self.doformat(p.down_code, x=locx, y=locy)
-                            gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
-                            gcode += self.doformat(p.lift_code, x=locx, y=locy)
-                            measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
-                            self.oldx = locx
-                            self.oldy = locy
                 log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
                 log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
             elif excellon_optimization_type == 'B':
             elif excellon_optimization_type == 'B':
                 log.debug("Using OR-Tools Basic drill path optimization.")
                 log.debug("Using OR-Tools Basic drill path optimization.")
-                for tool in tools:
-                    self.tool=tool
-                    self.postdata['toolC']=exobj.tools[tool]["C"]
-
-                    ################################################
-                    node_list = []
-                    locations = create_data_array()
-                    tsp_size = len(locations)
-                    num_routes = 1  # The number of routes, which is 1 in the TSP.
-
-                    # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
-                    depot = 0
-
-                    # Create routing model.
-                    if tsp_size > 0:
-                        routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
-                        search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
-
-                        # Callback to the distance function. The callback takes two
-                        # arguments (the from and to node indices) and returns the distance between them.
-                        dist_between_locations = CreateDistanceCallback()
-                        dist_callback = dist_between_locations.Distance
-                        routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
-
-                        # Solve, returns a solution if any.
-                        assignment = routing.SolveWithParameters(search_parameters)
-
-                        if assignment:
-                            # Solution cost.
-                            log.info("Total distance: " + str(assignment.ObjectiveValue()))
-
-                            # Inspect solution.
-                            # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
-                            route_number = 0
-                            node = routing.Start(route_number)
-                            start_node = node
-
-                            while not routing.IsEnd(node):
-                                node_list.append(node)
-                                node = assignment.Value(routing.NextVar(node))
+                if exobj.drills:
+                    for tool in tools:
+                        self.tool=tool
+                        self.postdata['toolC']=exobj.tools[tool]["C"]
+
+                        ################################################
+                        node_list = []
+                        locations = create_data_array()
+                        tsp_size = len(locations)
+                        num_routes = 1  # The number of routes, which is 1 in the TSP.
+
+                        # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
+                        depot = 0
+
+                        # Create routing model.
+                        if tsp_size > 0:
+                            routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
+                            search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
+
+                            # Callback to the distance function. The callback takes two
+                            # arguments (the from and to node indices) and returns the distance between them.
+                            dist_between_locations = CreateDistanceCallback()
+                            dist_callback = dist_between_locations.Distance
+                            routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
+
+                            # Solve, returns a solution if any.
+                            assignment = routing.SolveWithParameters(search_parameters)
+
+                            if assignment:
+                                # Solution cost.
+                                log.info("Total distance: " + str(assignment.ObjectiveValue()))
+
+                                # Inspect solution.
+                                # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
+                                route_number = 0
+                                node = routing.Start(route_number)
+                                start_node = node
+
+                                while not routing.IsEnd(node):
+                                    node_list.append(node)
+                                    node = assignment.Value(routing.NextVar(node))
+                            else:
+                                log.warning('No solution found.')
                         else:
                         else:
-                            log.warning('No solution found.')
-                    else:
-                        log.warning('Specify an instance greater than 0.')
-                    ################################################
+                            log.warning('Specify an instance greater than 0.')
+                        ################################################
+
+                        # Only if tool has points.
+                        if tool in points:
+                            # Tool change sequence (optional)
+                            if toolchange:
+                                gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
+                                gcode += self.doformat(p.spindle_code)  # Spindle start)
+                                if self.dwell is True:
+                                    gcode += self.doformat(p.dwell_code)  # Dwell time
+                            else:
+                                gcode += self.doformat(p.spindle_code)
+                                if self.dwell is True:
+                                    gcode += self.doformat(p.dwell_code)  # Dwell time
+
+                            # Drillling!
+                            for k in node_list:
+                                locx = locations[k][0]
+                                locy = locations[k][1]
+                                gcode += self.doformat(p.rapid_code, x=locx, y=locy)
+                                gcode += self.doformat(p.down_code, x=locx, y=locy)
+                                gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
+                                gcode += self.doformat(p.lift_code, x=locx, y=locy)
+                                measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
+                                self.oldx = locx
+                                self.oldy = locy
+                else:
+                    log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
+                              "The loaded Excellon file has no drills ...")
+                    self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...')
+                    return 'fail'
+
+                log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
+            else:
+                self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.")
+                return 'fail'
+        else:
+            log.debug("Using Travelling Salesman drill path optimization.")
+            for tool in tools:
+                if exobj.drills:
+                    self.tool = tool
+                    self.postdata['toolC'] = exobj.tools[tool]["C"]
 
 
                     # Only if tool has points.
                     # Only if tool has points.
                     if tool in points:
                     if tool in points:
                         # Tool change sequence (optional)
                         # Tool change sequence (optional)
                         if toolchange:
                         if toolchange:
-                            gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
+                            gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy))
                             gcode += self.doformat(p.spindle_code)  # Spindle start)
                             gcode += self.doformat(p.spindle_code)  # Spindle start)
                             if self.dwell is True:
                             if self.dwell is True:
                                 gcode += self.doformat(p.dwell_code)  # Dwell time
                                 gcode += self.doformat(p.dwell_code)  # Dwell time
@@ -4812,52 +4867,23 @@ class CNCjob(Geometry):
                                 gcode += self.doformat(p.dwell_code)  # Dwell time
                                 gcode += self.doformat(p.dwell_code)  # Dwell time
 
 
                         # Drillling!
                         # Drillling!
-                        for k in node_list:
-                            locx = locations[k][0]
-                            locy = locations[k][1]
-                            gcode += self.doformat(p.rapid_code, x=locx, y=locy)
-                            gcode += self.doformat(p.down_code, x=locx, y=locy)
-                            gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
-                            gcode += self.doformat(p.lift_code, x=locx, y=locy)
-                            measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
-                            self.oldx = locx
-                            self.oldy = locy
-                log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
-            else:
-                self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.")
-                return
-        else:
-            log.debug("Using Travelling Salesman drill path optimization.")
-            for tool in tools:
-                self.tool = tool
-                self.postdata['toolC'] = exobj.tools[tool]["C"]
-
-                # Only if tool has points.
-                if tool in points:
-                    # Tool change sequence (optional)
-                    if toolchange:
-                        gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy))
-                        gcode += self.doformat(p.spindle_code)  # Spindle start)
-                        if self.dwell is True:
-                            gcode += self.doformat(p.dwell_code)  # Dwell time
+                        altPoints = []
+                        for point in points[tool]:
+                            altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0]))
+
+                        for point in self.optimized_travelling_salesman(altPoints):
+                            gcode += self.doformat(p.rapid_code, x=point[0], y=point[1])
+                            gcode += self.doformat(p.down_code, x=point[0], y=point[1])
+                            gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1])
+                            gcode += self.doformat(p.lift_code, x=point[0], y=point[1])
+                            measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy))
+                            self.oldx = point[0]
+                            self.oldy = point[1]
                     else:
                     else:
-                        gcode += self.doformat(p.spindle_code)
-                        if self.dwell is True:
-                            gcode += self.doformat(p.dwell_code)  # Dwell time
-
-                    # Drillling!
-                    altPoints = []
-                    for point in points[tool]:
-                        altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0]))
-
-                    for point in self.optimized_travelling_salesman(altPoints):
-                        gcode += self.doformat(p.rapid_code, x=point[0], y=point[1])
-                        gcode += self.doformat(p.down_code, x=point[0], y=point[1])
-                        gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1])
-                        gcode += self.doformat(p.lift_code, x=point[0], y=point[1])
-                        measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy))
-                        self.oldx = point[0]
-                        self.oldy = point[1]
+                        log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
+                                  "The loaded Excellon file has no drills ...")
+                        self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...')
+                        return 'fail'
             log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
             log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
 
 
         gcode += self.doformat(p.spindle_stop_code)  # Spindle stop
         gcode += self.doformat(p.spindle_stop_code)  # Spindle stop
@@ -4867,6 +4893,7 @@ class CNCjob(Geometry):
         log.debug("The total travel distance including travel to end position is: %s" %
         log.debug("The total travel distance including travel to end position is: %s" %
                   str(measured_distance) + '\n')
                   str(measured_distance) + '\n')
         self.gcode = gcode
         self.gcode = gcode
+        return 'OK'
 
 
     def generate_from_multitool_geometry(self, geometry, append=True,
     def generate_from_multitool_geometry(self, geometry, append=True,
                                          tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0,
                                          tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0,
@@ -4912,25 +4939,25 @@ class CNCjob(Geometry):
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
         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.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.multidepth = multidepth
 
 
-        self.toolchangez = toolchangez
+        self.toolchangez = float(toolchangez) if toolchangez else None
 
 
         try:
         try:
             if toolchangexy == '':
             if toolchangexy == '':
@@ -5093,14 +5120,57 @@ class CNCjob(Geometry):
                                  "from a Geometry object without solid_geometry.")
                                  "from a Geometry object without solid_geometry.")
 
 
         temp_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:
         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:
             for it in geometry.solid_geometry:
                 # if the geometry is a closed shape then create a Polygon out of it
                 # if the geometry is a closed shape then create a Polygon out of it
                 if isinstance(it, LineString):
                 if isinstance(it, LineString):
                     c = it.coords
                     c = it.coords
                     if c[0] == c[-1]:
                     if c[0] == c[-1]:
                         it = Polygon(it)
                         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:
         else:
             temp_solid_geometry = geometry.solid_geometry
             temp_solid_geometry = geometry.solid_geometry
 
 
@@ -5108,25 +5178,33 @@ class CNCjob(Geometry):
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
         log.debug("%d paths" % len(flat_geometry))
         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.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.multidepth = multidepth
 
 
-        self.toolchangez = toolchangez
+        self.toolchangez = float(toolchangez) if toolchangez else None
 
 
         try:
         try:
             if toolchangexy == '':
             if toolchangexy == '':

+ 55 - 49
flatcamTools/ToolCalculators.py

@@ -21,6 +21,40 @@ class ToolCalculator(FlatCAMTool):
         title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
         title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
         self.layout.addWidget(title_label)
         self.layout.addWidget(title_label)
 
 
+        ######################
+        ## Units Calculator ##
+        ######################
+
+        self.unists_spacer_label = QtWidgets.QLabel(" ")
+        self.layout.addWidget(self.unists_spacer_label)
+
+        ## Title of the Units Calculator
+        units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName)
+        self.layout.addWidget(units_label)
+
+        #Grid Layout
+        grid_units_layout = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_units_layout)
+
+        inch_label = QtWidgets.QLabel("INCH")
+        mm_label = QtWidgets.QLabel("MM")
+        grid_units_layout.addWidget(mm_label, 0, 0)
+        grid_units_layout.addWidget( inch_label, 0, 1)
+
+        self.inch_entry = FCEntry()
+        # self.inch_entry.setFixedWidth(70)
+        self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+        self.inch_entry.setToolTip("Here you enter the value to be converted from INCH to MM")
+
+        self.mm_entry = FCEntry()
+        # self.mm_entry.setFixedWidth(130)
+        self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+        self.mm_entry.setToolTip("Here you enter the value to be converted from MM to INCH")
+
+        grid_units_layout.addWidget(self.mm_entry, 1, 0)
+        grid_units_layout.addWidget(self.inch_entry, 1, 1)
+
+
         ############################
         ############################
         ## V-shape Tool Calculator ##
         ## V-shape Tool Calculator ##
         ############################
         ############################
@@ -83,38 +117,6 @@ class ToolCalculator(FlatCAMTool):
 
 
         form_layout.addRow(self.empty_label, self.calculate_vshape_button)
         form_layout.addRow(self.empty_label, self.calculate_vshape_button)
 
 
-        ######################
-        ## Units Calculator ##
-        ######################
-
-        self.unists_spacer_label = QtWidgets.QLabel(" ")
-        self.layout.addWidget(self.unists_spacer_label)
-
-        ## Title of the Units Calculator
-        units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName)
-        self.layout.addWidget(units_label)
-
-        #Grid Layout
-        grid_units_layout = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid_units_layout)
-
-        inch_label = QtWidgets.QLabel("INCH")
-        mm_label = QtWidgets.QLabel("MM")
-        grid_units_layout.addWidget(mm_label, 0, 0)
-        grid_units_layout.addWidget( inch_label, 0, 1)
-
-        self.inch_entry = FCEntry()
-        # self.inch_entry.setFixedWidth(70)
-        self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.inch_entry.setToolTip("Here you enter the value to be converted from INCH to MM")
-
-        self.mm_entry = FCEntry()
-        # self.mm_entry.setFixedWidth(130)
-        self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
-        self.mm_entry.setToolTip("Here you enter the value to be converted from MM to INCH")
-
-        grid_units_layout.addWidget(self.mm_entry, 1, 0)
-        grid_units_layout.addWidget(self.inch_entry, 1, 1)
 
 
         ####################################
         ####################################
         ## ElectroPlating Tool Calculator ##
         ## ElectroPlating Tool Calculator ##
@@ -224,27 +226,31 @@ class ToolCalculator(FlatCAMTool):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
 
 
     def set_tool_ui(self):
     def set_tool_ui(self):
+        self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
+
         ## Initialize form
         ## Initialize form
         self.mm_entry.set_value('0')
         self.mm_entry.set_value('0')
         self.inch_entry.set_value('0')
         self.inch_entry.set_value('0')
 
 
-        self.pcblength_entry.set_value('10')
-        self.pcbwidth_entry.set_value('10')
-        self.cdensity_entry.set_value('13')
-        self.growth_entry.set_value('10')
-        self.cvalue_entry.set_value(2.80)
-        self.time_entry.set_value(33.0)
-
-        if self.app.defaults["units"] == 'MM':
-            self.tipDia_entry.set_value('0.2')
-            self.tipAngle_entry.set_value('45')
-            self.cutDepth_entry.set_value('0.25')
-            self.effectiveToolDia_entry.set_value('0.39')
-        else:
-            self.tipDia_entry.set_value('7.87402')
-            self.tipAngle_entry.set_value('45')
-            self.cutDepth_entry.set_value('9.84252')
-            self.effectiveToolDia_entry.set_value('15.35433')
+        length = self.app.defaults["tools_calc_electro_length"]
+        width = self.app.defaults["tools_calc_electro_width"]
+        density = self.app.defaults["tools_calc_electro_cdensity"]
+        growth = self.app.defaults["tools_calc_electro_growth"]
+        self.pcblength_entry.set_value(length)
+        self.pcbwidth_entry.set_value(width)
+        self.cdensity_entry.set_value(density)
+        self.growth_entry.set_value(growth)
+        self.cvalue_entry.set_value(0.00)
+        self.time_entry.set_value(0.0)
+
+        tip_dia = self.app.defaults["tools_calc_vshape_tip_dia"]
+        tip_angle = self.app.defaults["tools_calc_vshape_tip_angle"]
+        cut_z = self.app.defaults["tools_calc_vshape_cut_z"]
+
+        self.tipDia_entry.set_value(tip_dia)
+        self.tipAngle_entry.set_value(tip_angle)
+        self.cutDepth_entry.set_value(cut_z)
+        self.effectiveToolDia_entry.set_value('0.0000')
 
 
     def on_calculate_tool_dia(self):
     def on_calculate_tool_dia(self):
         # Calculation:
         # Calculation:

+ 2 - 7
flatcamTools/ToolMeasurement.py

@@ -184,25 +184,22 @@ class Measurement(FlatCAMTool):
             # disconnect the mouse/key events from functions of measurement tool
             # disconnect the mouse/key events from functions of measurement tool
             self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
             self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
             self.app.plotcanvas.vis_disconnect('mouse_press', self.on_click_meas)
             self.app.plotcanvas.vis_disconnect('mouse_press', self.on_click_meas)
-            self.app.plotcanvas.vis_disconnect('key_release', self.on_key_release_meas)
 
 
             # reconnect the mouse/key events to the functions from where the tool was called
             # reconnect the mouse/key events to the functions from where the tool was called
             if self.app.call_source == 'app':
             if self.app.call_source == 'app':
                 self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
                 self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
                 self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
                 self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-                self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
+                self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
                 self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
                 self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
             elif self.app.call_source == 'geo_editor':
             elif self.app.call_source == 'geo_editor':
                 self.app.geo_editor.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
                 self.app.geo_editor.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
                 self.app.geo_editor.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
                 self.app.geo_editor.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
                 self.app.geo_editor.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key)
                 self.app.geo_editor.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key)
-                self.app.geo_editor.canvas.vis_connect('key_release', self.app.geo_editor.on_canvas_key_release)
                 self.app.geo_editor.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
                 self.app.geo_editor.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
             elif self.app.call_source == 'exc_editor':
             elif self.app.call_source == 'exc_editor':
                 self.app.exc_editor.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
                 self.app.exc_editor.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
                 self.app.exc_editor.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
                 self.app.exc_editor.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
                 self.app.exc_editor.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key)
                 self.app.exc_editor.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key)
-                self.app.exc_editor.canvas.vis_connect('key_release', self.app.exc_editor.on_canvas_key_release)
                 self.app.exc_editor.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
                 self.app.exc_editor.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
 
 
             self.clicked_meas = 0
             self.clicked_meas = 0
@@ -219,19 +216,17 @@ class Measurement(FlatCAMTool):
             if self.app.call_source == 'app':
             if self.app.call_source == 'app':
                 self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
                 self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
                 self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
                 self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
-                self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot)
+                self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
                 self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
                 self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
             elif self.app.call_source == 'geo_editor':
             elif self.app.call_source == 'geo_editor':
                 self.app.geo_editor.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
                 self.app.geo_editor.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
                 self.app.geo_editor.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
                 self.app.geo_editor.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
                 self.app.geo_editor.canvas.vis_disconnect('key_press', self.app.geo_editor.on_canvas_key)
                 self.app.geo_editor.canvas.vis_disconnect('key_press', self.app.geo_editor.on_canvas_key)
-                self.app.geo_editor.canvas.vis_disconnect('key_release', self.app.geo_editor.on_canvas_key_release)
                 self.app.geo_editor.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_canvas_click_release)
                 self.app.geo_editor.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_canvas_click_release)
             elif self.app.call_source == 'exc_editor':
             elif self.app.call_source == 'exc_editor':
                 self.app.exc_editor.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
                 self.app.exc_editor.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
                 self.app.exc_editor.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
                 self.app.exc_editor.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
                 self.app.exc_editor.canvas.vis_disconnect('key_press', self.app.exc_editor.on_canvas_key)
                 self.app.exc_editor.canvas.vis_disconnect('key_press', self.app.exc_editor.on_canvas_key)
-                self.app.exc_editor.canvas.vis_disconnect('key_release', self.app.exc_editor.on_canvas_key_release)
                 self.app.exc_editor.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_canvas_click_release)
                 self.app.exc_editor.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_canvas_click_release)
 
 
             # we can safely connect the app mouse events to the measurement tool
             # we can safely connect the app mouse events to the measurement tool

+ 3 - 2
flatcamTools/ToolMove.py

@@ -47,7 +47,7 @@ class ToolMove(FlatCAMTool):
             self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
             self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
             self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click)
             self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click)
             self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press)
             self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press)
-            self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
+            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
 
 
             self.clicked_move = 0
             self.clicked_move = 0
 
 
@@ -139,12 +139,13 @@ class ToolMove(FlatCAMTool):
                         proc.done()
                         proc.done()
                         # delete the selection bounding box
                         # delete the selection bounding box
                         self.delete_shape()
                         self.delete_shape()
+                        self.app.inform.emit('[success]%s object was moved ...' %
+                                             str(sel_obj.kind).capitalize())
 
 
                     self.app.worker_task.emit({'fcn': job_move, 'params': [self]})
                     self.app.worker_task.emit({'fcn': job_move, 'params': [self]})
 
 
                     self.clicked_move = 0
                     self.clicked_move = 0
                     self.toggle()
                     self.toggle()
-                    self.app.inform.emit("[success]Object was moved ...")
                     return
                     return
 
 
                 except TypeError:
                 except TypeError:

+ 6 - 2
flatcamTools/ToolNonCopperClear.py

@@ -5,6 +5,7 @@ from copy import copy,deepcopy
 from ObjectCollection import *
 from ObjectCollection import *
 import time
 import time
 
 
+
 class NonCopperClear(FlatCAMTool, Gerber):
 class NonCopperClear(FlatCAMTool, Gerber):
 
 
     toolName = "Non-Copper Clearing"
     toolName = "Non-Copper Clearing"
@@ -446,6 +447,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
                 self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
                 return
                 return
 
 
+        if tool_dia == 0:
+            self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
+            return
+
         # construct a list of all 'tooluid' in the self.tools
         # construct a list of all 'tooluid' in the self.tools
         tool_uid_list = []
         tool_uid_list = []
         for tooluid_key in self.ncc_tools:
         for tooluid_key in self.ncc_tools:
@@ -618,7 +623,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
             self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % self.obj_name)
             self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % self.obj_name)
             return "Could not retrieve object: %s" % self.obj_name
             return "Could not retrieve object: %s" % self.obj_name
 
 
-
         # Prepare non-copper polygons
         # Prepare non-copper polygons
         try:
         try:
             bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre)
             bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre)
@@ -626,7 +630,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
             self.app.inform.emit("[ERROR_NOTCL]No Gerber file available.")
             self.app.inform.emit("[ERROR_NOTCL]No Gerber file available.")
             return
             return
 
 
-        # calculate the empty area by substracting the solid_geometry from the object bounding box geometry
+        # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry
         empty = self.ncc_obj.get_empty_area(bounding_box)
         empty = self.ncc_obj.get_empty_area(bounding_box)
         if type(empty) is Polygon:
         if type(empty) is Polygon:
             empty = MultiPolygon([empty])
             empty = MultiPolygon([empty])

+ 33 - 0
flatcamTools/ToolPaint.py

@@ -750,7 +750,9 @@ class ToolPaint(FlatCAMTool, Gerber):
                                     overlap=overlap,
                                     overlap=overlap,
                                     connect=connect,
                                     connect=connect,
                                     contour=contour)
                                     contour=contour)
+                    self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
 
 
+            self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
             self.app.plotcanvas.vis_connect('mouse_press', doit)
             self.app.plotcanvas.vis_connect('mouse_press', doit)
 
 
     def paint_poly(self, obj, inside_pt, tooldia, overlap,
     def paint_poly(self, obj, inside_pt, tooldia, overlap,
@@ -839,6 +841,17 @@ class ToolPaint(FlatCAMTool, Gerber):
                     return None
                     return None
 
 
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
+
+            try:
+                a, b, c, d = poly.bounds()
+                geo_obj.options['xmin'] = a
+                geo_obj.options['ymin'] = b
+                geo_obj.options['xmax'] = c
+                geo_obj.options['ymax'] = d
+            except Exception as e:
+                log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
+                return
+
             try:
             try:
                 poly_buf = poly.buffer(-paint_margin)
                 poly_buf = poly.buffer(-paint_margin)
                 if isinstance(poly_buf, MultiPolygon):
                 if isinstance(poly_buf, MultiPolygon):
@@ -988,6 +1001,16 @@ class ToolPaint(FlatCAMTool, Gerber):
                 sorted_tools.append(float(self.tools_table.item(row, 1).text()))
                 sorted_tools.append(float(self.tools_table.item(row, 1).text()))
             sorted_tools.sort(reverse=True)
             sorted_tools.sort(reverse=True)
 
 
+            try:
+                a, b, c, d = obj.bounds()
+                geo_obj.options['xmin'] = a
+                geo_obj.options['ymin'] = b
+                geo_obj.options['xmax'] = c
+                geo_obj.options['ymax'] = d
+            except Exception as e:
+                log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
+                return
+
             total_geometry = []
             total_geometry = []
             current_uid = int(1)
             current_uid = int(1)
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
@@ -1085,6 +1108,16 @@ class ToolPaint(FlatCAMTool, Gerber):
             current_uid = int(1)
             current_uid = int(1)
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
 
 
+            try:
+                a, b, c, d = obj.bounds()
+                geo_obj.options['xmin'] = a
+                geo_obj.options['ymin'] = b
+                geo_obj.options['xmax'] = c
+                geo_obj.options['ymax'] = d
+            except Exception as e:
+                log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
+                return
+
             for tool_dia in sorted_tools:
             for tool_dia in sorted_tools:
                 for geo in recurse(obj.solid_geometry):
                 for geo in recurse(obj.solid_geometry):
                     try:
                     try:

+ 14 - 159
flatcamTools/ToolShell.py

@@ -6,165 +6,12 @@
 # MIT Licence                                              #
 # MIT Licence                                              #
 ############################################################
 ############################################################
 
 
+# from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import QVBoxLayout, QWidget
+from GUIElements import _BrowserTextEdit, _ExpandableTextEdit
 import html
 import html
-from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtCore import Qt, QStringListModel
-from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor
-from PyQt5.QtWidgets import QLineEdit, QSizePolicy, QTextEdit, QVBoxLayout, QWidget, QCompleter, QAction
-
-class _BrowserTextEdit(QTextEdit):
-
-    def __init__(self, version):
-        QTextEdit.__init__(self)
-        self.menu = None
-        self.version = version
-
-    def contextMenuEvent(self, event):
-        self.menu = self.createStandardContextMenu(event.pos())
-        clear_action = QAction("Clear", self)
-        clear_action.setShortcut(QKeySequence(Qt.Key_Delete))   # it's not working, the shortcut
-        self.menu.addAction(clear_action)
-        clear_action.triggered.connect(self.clear)
-        self.menu.exec_(event.globalPos())
-
-
-    def clear(self):
-        QTextEdit.clear(self)
-        text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version
-        text = html.escape(text)
-        text = text.replace('\n', '<br/>')
-        self.moveCursor(QTextCursor.End)
-        self.insertHtml(text)
-
-class _ExpandableTextEdit(QTextEdit):
-    """
-    Class implements edit line, which expands themselves automatically
-    """
-
-    historyNext = pyqtSignal()
-    historyPrev = pyqtSignal()
-
-    def __init__(self, termwidget, *args):
-        QTextEdit.__init__(self, *args)
-        self.setStyleSheet("font: 9pt \"Courier\";")
-        self._fittedHeight = 1
-        self.textChanged.connect(self._fit_to_document)
-        self._fit_to_document()
-        self._termWidget = termwidget
-
-        self.completer = MyCompleter()
-
-        self.model = QStringListModel()
-        self.completer.setModel(self.model)
-        self.set_model_data(keyword_list=[])
-        self.completer.insertText.connect(self.insertCompletion)
-
-    def set_model_data(self, keyword_list):
-        self.model.setStringList(keyword_list)
-
-    def insertCompletion(self, completion):
-        tc = self.textCursor()
-        extra = (len(completion) - len(self.completer.completionPrefix()))
-        tc.movePosition(QTextCursor.Left)
-        tc.movePosition(QTextCursor.EndOfWord)
-        tc.insertText(completion[-extra:])
-        self.setTextCursor(tc)
-        self.completer.popup().hide()
-
-    def focusInEvent(self, event):
-        if self.completer:
-            self.completer.setWidget(self)
-        QTextEdit.focusInEvent(self, event)
-
-    def keyPressEvent(self, event):
-        """
-        Catch keyboard events. Process Enter, Up, Down
-        """
-        if event.matches(QKeySequence.InsertParagraphSeparator):
-            text = self.toPlainText()
-            if self._termWidget.is_command_complete(text):
-                self._termWidget.exec_current_command()
-                return
-        elif event.matches(QKeySequence.MoveToNextLine):
-            text = self.toPlainText()
-            cursor_pos = self.textCursor().position()
-            textBeforeEnd = text[cursor_pos:]
-
-            if len(textBeforeEnd.split('\n')) <= 1:
-                self.historyNext.emit()
-                return
-        elif event.matches(QKeySequence.MoveToPreviousLine):
-            text = self.toPlainText()
-            cursor_pos = self.textCursor().position()
-            text_before_start = text[:cursor_pos]
-            # lineCount = len(textBeforeStart.splitlines())
-            line_count = len(text_before_start.split('\n'))
-            if len(text_before_start) > 0 and \
-                    (text_before_start[-1] == '\n' or text_before_start[-1] == '\r'):
-                line_count += 1
-            if line_count <= 1:
-                self.historyPrev.emit()
-                return
-        elif event.matches(QKeySequence.MoveToNextPage) or \
-                event.matches(QKeySequence.MoveToPreviousPage):
-            return self._termWidget.browser().keyPressEvent(event)
-
-        tc = self.textCursor()
-        if event.key() == Qt.Key_Tab and self.completer.popup().isVisible():
-            self.completer.insertText.emit(self.completer.getSelected())
-            self.completer.setCompletionMode(QCompleter.PopupCompletion)
-            return
-
-        QTextEdit.keyPressEvent(self, event)
-        tc.select(QTextCursor.WordUnderCursor)
-        cr = self.cursorRect()
-
-        if len(tc.selectedText()) > 0:
-            self.completer.setCompletionPrefix(tc.selectedText())
-            popup = self.completer.popup()
-            popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
-
-            cr.setWidth(self.completer.popup().sizeHintForColumn(0)
-                        + self.completer.popup().verticalScrollBar().sizeHint().width())
-            self.completer.complete(cr)
-        else:
-            self.completer.popup().hide()
-
-    def sizeHint(self):
-        """
-        QWidget sizeHint impelemtation
-        """
-        hint = QTextEdit.sizeHint(self)
-        hint.setHeight(self._fittedHeight)
-        return hint
-
-    def _fit_to_document(self):
-        """
-        Update widget height to fit all text
-        """
-        documentsize = self.document().size().toSize()
-        self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height())
-        self.setMaximumHeight(self._fittedHeight)
-        self.updateGeometry()
-
-    def insertFromMimeData(self, mime_data):
-        # Paste only plain text.
-        self.insertPlainText(mime_data.text())
-
-
-class MyCompleter(QCompleter):
-    insertText = pyqtSignal(str)
-
-    def __init__(self, parent=None):
-        QCompleter.__init__(self)
-        self.setCompletionMode(QCompleter.PopupCompletion)
-        self.highlighted.connect(self.setHighlighted)
-
-    def setHighlighted(self, text):
-        self.lastSelected = text
-
-    def getSelected(self):
-        return self.lastSelected
 
 
 
 
 class TermWidget(QWidget):
 class TermWidget(QWidget):
@@ -234,7 +81,7 @@ class TermWidget(QWidget):
         """
         """
         Convert text to HTML for inserting it to browser
         Convert text to HTML for inserting it to browser
         """
         """
-        assert style in ('in', 'out', 'err', 'warning', 'success')
+        assert style in ('in', 'out', 'err', 'warning', 'success', 'selected')
 
 
         text = html.escape(text)
         text = html.escape(text)
         text = text.replace('\n', '<br/>')
         text = text.replace('\n', '<br/>')
@@ -247,6 +94,8 @@ class TermWidget(QWidget):
             text = '<span style="font-weight: bold; color: rgb(244, 182, 66);">%s</span>' % text
             text = '<span style="font-weight: bold; color: rgb(244, 182, 66);">%s</span>' % text
         elif style == 'success':
         elif style == 'success':
             text = '<span style="font-weight: bold; color: rgb(8, 68, 0);">%s</span>' % text
             text = '<span style="font-weight: bold; color: rgb(8, 68, 0);">%s</span>' % text
+        elif style == 'selected':
+            text = '<span style="font-weight: bold; color: rgb(0, 8, 255);">%s</span>' % text
         else:
         else:
             text = '<span>%s</span>' % text  # without span <br/> is ignored!!!
             text = '<span>%s</span>' % text  # without span <br/> is ignored!!!
 
 
@@ -313,6 +162,11 @@ class TermWidget(QWidget):
         """
         """
         self._append_to_browser('success', text)
         self._append_to_browser('success', text)
 
 
+    def append_selected(self, text):
+        """Appent text to output widget
+        """
+        self._append_to_browser('selected', text)
+
     def append_warning(self, text):
     def append_warning(self, text):
         """Appent text to output widget
         """Appent text to output widget
         """
         """
@@ -352,6 +206,7 @@ class TermWidget(QWidget):
             self._edit.setPlainText(self._history[self._historyIndex])
             self._edit.setPlainText(self._history[self._historyIndex])
             self._edit.moveCursor(QTextCursor.End)
             self._edit.moveCursor(QTextCursor.End)
 
 
+
 class FCShell(TermWidget):
 class FCShell(TermWidget):
     def __init__(self, sysShell, version, *args):
     def __init__(self, sysShell, version, *args):
         TermWidget.__init__(self, version, *args)
         TermWidget.__init__(self, version, *args)

+ 1 - 1
postprocessors/default.py

@@ -45,7 +45,7 @@ class default(FlatCAMPostProc):
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
         gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
 
 
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
         if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
-            gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
+            gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
         else:
         else:
             gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
             gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
 
 

BIN
share/about32.png


BIN
share/bluelight12.png


BIN
share/letter_t_32.png


BIN
share/youtube32.png


+ 0 - 179
tclCommands/TclCommandCutoutAny.py

@@ -1,179 +0,0 @@
-from ObjectCollection import *
-from tclCommands.TclCommand import TclCommand
-
-
-class TclCommandCutoutAny(TclCommand):
-    """
-    Tcl shell command to create a board cutout geometry. Allow cutout for any shape.
-
-    example:
-
-    """
-
-    # List of all command aliases, to be able use old
-    # names for backward compatibility (add_poly, add_polygon)
-    aliases = ['cutout_any', 'cut_any']
-
-    # Dictionary of types from Tcl command, needs to be ordered
-    arg_names = collections.OrderedDict([
-        ('name', str),
-    ])
-
-    # Dictionary of types from Tcl command, needs to be ordered,
-    # this  is  for options  like -optionname value
-    option_types = collections.OrderedDict([
-        ('dia', float),
-        ('margin', float),
-        ('gapsize', float),
-        ('gaps', str)
-    ])
-
-    # array of mandatory options for current Tcl command: required = {'name','outname'}
-    required = ['name']
-
-    # structured help for current command, args needs to be ordered
-    help = {
-        'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape',
-        'args': collections.OrderedDict([
-            ('name', 'Name of the object.'),
-            ('dia', 'Tool diameter.'),
-            ('margin', 'Margin over bounds.'),
-            ('gapsize', 'size of gap.'),
-            ('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, "
-                     "'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts")
-        ]),
-        'examples': []
-    }
-
-    def execute(self, args, unnamed_args):
-        """
-
-        :param args:
-        :param unnamed_args:
-        :return:
-        """
-
-        def subtract_rectangle(obj_, x0, y0, x1, y1):
-            pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
-            obj_.subtract_polygon(pts)
-
-        if 'name' in args:
-            name = args['name']
-        else:
-            self.app.inform.emit(
-                "[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
-            return
-
-        if 'margin' in args:
-            margin = args['margin']
-        else:
-            margin = 0.001
-
-        if 'dia' in args:
-            dia = args['dia']
-        else:
-            dia = 0.1
-
-        if 'gaps' in args:
-            gaps = args['gaps']
-        else:
-            gaps = 4
-
-        if 'gapsize' in args:
-            gapsize = args['gapsize']
-        else:
-            gapsize = 0.1
-
-        # Get source object.
-        try:
-            cutout_obj = self.app.collection.get_by_name(str(name))
-        except:
-            return "Could not retrieve object: %s" % name
-
-        if 0 in {dia}:
-            self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.")
-            return "Tool Diameter is zero value. Change it to a positive integer."
-
-        if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]:
-            self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
-                                 "Fill in a correct value and retry. ")
-            return
-
-        # Get min and max data for each object as we just cut rectangles across X or Y
-        xmin, ymin, xmax, ymax = cutout_obj.bounds()
-        px = 0.5 * (xmin + xmax) + margin
-        py = 0.5 * (ymin + ymax) + margin
-        lenghtx = (xmax - xmin) + (margin * 2)
-        lenghty = (ymax - ymin) + (margin * 2)
-
-        gapsize = gapsize + (dia / 2)
-
-        if isinstance(cutout_obj, FlatCAMGeometry):
-            # rename the obj name so it can be identified as cutout
-            cutout_obj.options["name"] += "_cutout"
-        elif isinstance(cutout_obj, FlatCAMGerber):
-            cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp")
-            ext_obj = self.app.collection.get_by_name("_temp")
-
-            def geo_init(geo_obj, app_obj):
-                geo_obj.solid_geometry = obj_exteriors
-
-            outname = cutout_obj.options["name"] + "_cutout"
-
-            obj_exteriors = ext_obj.get_exteriors()
-            self.app.new_object('geometry', outname, geo_init)
-
-            self.app.collection.set_all_inactive()
-            self.app.collection.set_active("_temp")
-            self.app.on_delete()
-
-            cutout_obj = self.app.collection.get_by_name(outname)
-        else:
-            self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
-            return
-
-        try:
-            gaps_u = int(gaps)
-        except ValueError:
-            gaps_u = gaps
-
-        if gaps_u == 8 or gaps_u == '2lr':
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,              # botleft_x
-                               py - gapsize + lenghty / 4,  # botleft_y
-                               xmax + gapsize,              # topright_x
-                               py + gapsize + lenghty / 4)  # topright_y
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,
-                               py - gapsize - lenghty / 4,
-                               xmax + gapsize,
-                               py + gapsize - lenghty / 4)
-
-        if gaps_u == 8 or gaps_u == '2tb':
-            subtract_rectangle(cutout_obj,
-                               px - gapsize + lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize + lenghtx / 4,
-                               ymax + gapsize)
-            subtract_rectangle(cutout_obj,
-                               px - gapsize - lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize - lenghtx / 4,
-                               ymax + gapsize)
-
-        if gaps_u == 4 or gaps_u == 'lr':
-            subtract_rectangle(cutout_obj,
-                               xmin - gapsize,
-                               py - gapsize,
-                               xmax + gapsize,
-                               py + gapsize)
-
-        if gaps_u == 4 or gaps_u == 'tb':
-            subtract_rectangle(cutout_obj,
-                               px - gapsize,
-                               ymin - gapsize,
-                               px + gapsize,
-                               ymax + gapsize)
-
-        cutout_obj.plot()
-        self.app.inform.emit("[success]Any-form Cutout operation finished.")

+ 239 - 85
tclCommands/TclCommandGeoCutout.py

@@ -1,23 +1,26 @@
 from ObjectCollection import *
 from ObjectCollection import *
 from tclCommands.TclCommand import TclCommandSignaled
 from tclCommands.TclCommand import TclCommandSignaled
-
+from copy import deepcopy
 
 
 class TclCommandGeoCutout(TclCommandSignaled):
 class TclCommandGeoCutout(TclCommandSignaled):
     """
     """
-    Tcl shell command to cut holding gaps from geometry.
-    """
+        Tcl shell command to create a board cutout geometry. Allow cutout for any shape. Cuts holding gaps from geometry.
+
+        example:
 
 
-    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
-    aliases = ['geocutout']
+        """
+
+    # List of all command aliases, to be able use old
+    # names for backward compatibility (add_poly, add_polygon)
+    aliases = ['geocutout', 'geoc']
 
 
-    # Dictionary of types from Tcl command, needs to be ordered.
-    # For positional arguments
+    # Dictionary of types from Tcl command, needs to be ordered
     arg_names = collections.OrderedDict([
     arg_names = collections.OrderedDict([
-        ('name', str)
+        ('name', str),
     ])
     ])
 
 
-    # Dictionary of types from Tcl command, needs to be ordered.
-    # For options like -optionname value
+    # Dictionary of types from Tcl command, needs to be ordered,
+    # this  is  for options  like -optionname value
     option_types = collections.OrderedDict([
     option_types = collections.OrderedDict([
         ('dia', float),
         ('dia', float),
         ('margin', float),
         ('margin', float),
@@ -30,99 +33,250 @@ class TclCommandGeoCutout(TclCommandSignaled):
 
 
     # structured help for current command, args needs to be ordered
     # structured help for current command, args needs to be ordered
     help = {
     help = {
-        'main': "Cut holding gaps from geometry.",
+        'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape',
         'args': collections.OrderedDict([
         'args': collections.OrderedDict([
-            ('name', 'Name of the geometry object.'),
+            ('name', 'Name of the object.'),
             ('dia', 'Tool diameter.'),
             ('dia', 'Tool diameter.'),
             ('margin', 'Margin over bounds.'),
             ('margin', 'Margin over bounds.'),
-            ('gapsize', 'Size of gap.'),
-            ('gaps', 'Type of gaps.'),
+            ('gapsize', 'size of gap.'),
+            ('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, "
+                     "'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts")
         ]),
         ]),
         'examples': ["      #isolate margin for example from fritzing arduino shield or any svg etc\n" +
         'examples': ["      #isolate margin for example from fritzing arduino shield or any svg etc\n" +
-                        "      isolate BCu_margin -dia 3 -overlap 1\n" +
-                        "\n" +
-                        "      #create exteriors from isolated object\n" +
-                        "      exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" +
-                        "\n" +
-                        "      #delete isolated object if you dond need id anymore\n" +
-                        "      delete BCu_margin_iso\n" +
-                        "\n" +
-                        "      #finally cut holding gaps\n" +
-                        "      geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"]
+                     "      isolate BCu_margin -dia 3 -overlap 1\n" +
+                     "\n" +
+                     "      #create exteriors from isolated object\n" +
+                     "      exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" +
+                     "\n" +
+                     "      #delete isolated object if you dond need id anymore\n" +
+                     "      delete BCu_margin_iso\n" +
+                     "\n" +
+                     "      #finally cut holding gaps\n" +
+                     "      geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"]
     }
     }
 
 
+    flat_geometry = []
+
     def execute(self, args, unnamed_args):
     def execute(self, args, unnamed_args):
         """
         """
-        execute current TCL shell command
 
 
-        :param args: array of known named arguments and options
-        :param unnamed_args: array of other values which were passed into command
-            without -somename and  we do not have them in known arg_names
-        :return: None or exception
+        :param args:
+        :param unnamed_args:
+        :return:
         """
         """
 
 
-        # How gaps wil be rendered:
-        # lr    - left + right
-        # tb    - top + bottom
-        # 4     - left + right +top + bottom
-        # 2lr   - 2*left + 2*right
-        # 2tb   - 2*top + 2*bottom
-        # 8     - 2*left + 2*right +2*top + 2*bottom
-
-        name = args['name']
-        obj = None
 
 
         def subtract_rectangle(obj_, x0, y0, x1, y1):
         def subtract_rectangle(obj_, x0, y0, x1, y1):
             pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
             pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
             obj_.subtract_polygon(pts)
             obj_.subtract_polygon(pts)
 
 
+        def substract_rectangle_geo(geo, x0, y0, x1, y1):
+            pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
+
+
+            def flatten(geometry=None, reset=True, pathonly=False):
+                """
+                Creates a list of non-iterable linear geometry objects.
+                Polygons are expanded into its exterior and interiors if specified.
+
+                Results are placed in flat_geometry
+
+                :param geometry: Shapely type or list or list of list of such.
+                :param reset: Clears the contents of self.flat_geometry.
+                :param pathonly: Expands polygons into linear elements.
+                """
+
+                if reset:
+                    self.flat_geometry = []
+
+                ## If iterable, expand recursively.
+                try:
+                    for geo in geometry:
+                        if geo is not None:
+                            flatten(geometry=geo,
+                                    reset=False,
+                                    pathonly=pathonly)
+
+                ## Not iterable, do the actual indexing and add.
+                except TypeError:
+                    if pathonly and type(geometry) == Polygon:
+                        self.flat_geometry.append(geometry.exterior)
+                        flatten(geometry=geometry.interiors,
+                                reset=False,
+                                pathonly=True)
+                    else:
+                        self.flat_geometry.append(geometry)
+
+                return self.flat_geometry
+
+            flat_geometry = flatten(geo, pathonly=True)
+
+            polygon = Polygon(pts)
+            toolgeo = cascaded_union(polygon)
+            diffs = []
+            for target in flat_geometry:
+                if type(target) == LineString or type(target) == LinearRing:
+                    diffs.append(target.difference(toolgeo))
+                else:
+                    log.warning("Not implemented.")
+            return cascaded_union(diffs)
+
+        if 'name' in args:
+            name = args['name']
+        else:
+            self.app.inform.emit(
+                "[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
+            return
+
+        if 'margin' in args:
+            margin = args['margin']
+        else:
+            margin = 0.001
+
+        if 'dia' in args:
+            dia = args['dia']
+        else:
+            dia = 0.1
+
+        if 'gaps' in args:
+            gaps = args['gaps']
+        else:
+            gaps = 4
+
+        if 'gapsize' in args:
+            gapsize = args['gapsize']
+        else:
+            gapsize = 0.1
+
+        # Get source object.
         try:
         try:
-            obj = self.app.collection.get_by_name(str(name))
+            cutout_obj = self.app.collection.get_by_name(str(name))
         except:
         except:
-            self.raise_tcl_error("Could not retrieve object: %s" % name)
+            return "Could not retrieve object: %s" % name
+
+        if 0 in {dia}:
+            self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.")
+            return "Tool Diameter is zero value. Change it to a positive integer."
+
+        if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]:
+            self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
+                                 "Fill in a correct value and retry. ")
+            return
 
 
         # Get min and max data for each object as we just cut rectangles across X or Y
         # Get min and max data for each object as we just cut rectangles across X or Y
-        xmin, ymin, xmax, ymax = obj.bounds()
-        px = 0.5 * (xmin + xmax)
-        py = 0.5 * (ymin + ymax)
-        lenghtx = (xmax - xmin)
-        lenghty = (ymax - ymin)
-        gapsize = args['gapsize'] + (args['dia'] / 2)
-
-        if args['gaps'] == '8' or args['gaps'] == '2lr':
-            subtract_rectangle(obj,
-                               xmin - gapsize,  # botleft_x
-                               py - gapsize + lenghty / 4,  # botleft_y
-                               xmax + gapsize,  # topright_x
-                               py + gapsize + lenghty / 4)  # topright_y
-            subtract_rectangle(obj,
-                               xmin - gapsize,
-                               py - gapsize - lenghty / 4,
-                               xmax + gapsize,
-                               py + gapsize - lenghty / 4)
-
-        if args['gaps'] == '8' or args['gaps'] == '2tb':
-            subtract_rectangle(obj,
-                               px - gapsize + lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize + lenghtx / 4,
-                               ymax + gapsize)
-            subtract_rectangle(obj,
-                               px - gapsize - lenghtx / 4,
-                               ymin - gapsize,
-                               px + gapsize - lenghtx / 4,
-                               ymax + gapsize)
-
-        if args['gaps'] == '4' or args['gaps'] == 'lr':
-            subtract_rectangle(obj,
-                               xmin - gapsize,
-                               py - gapsize,
-                               xmax + gapsize,
-                               py + gapsize)
-
-        if args['gaps'] == '4' or args['gaps'] == 'tb':
-            subtract_rectangle(obj,
-                               px - gapsize,
-                               ymin - gapsize,
-                               px + gapsize,
-                               ymax + gapsize)
+        xmin, ymin, xmax, ymax = cutout_obj.bounds()
+        px = 0.5 * (xmin + xmax) + margin
+        py = 0.5 * (ymin + ymax) + margin
+        lenghtx = (xmax - xmin) + (margin * 2)
+        lenghty = (ymax - ymin) + (margin * 2)
+
+        gapsize = gapsize / 2 + (dia / 2)
+
+        try:
+            gaps_u = int(gaps)
+        except ValueError:
+            gaps_u = gaps
+
+        if isinstance(cutout_obj, FlatCAMGeometry):
+            # rename the obj name so it can be identified as cutout
+            cutout_obj.options["name"] += "_cutout"
+
+            if gaps_u == 8 or gaps_u == '2lr':
+                subtract_rectangle(cutout_obj,
+                                   xmin - gapsize,  # botleft_x
+                                   py - gapsize + lenghty / 4,  # botleft_y
+                                   xmax + gapsize,  # topright_x
+                                   py + gapsize + lenghty / 4)  # topright_y
+                subtract_rectangle(cutout_obj,
+                                   xmin - gapsize,
+                                   py - gapsize - lenghty / 4,
+                                   xmax + gapsize,
+                                   py + gapsize - lenghty / 4)
+
+            if gaps_u == 8 or gaps_u == '2tb':
+                subtract_rectangle(cutout_obj,
+                                   px - gapsize + lenghtx / 4,
+                                   ymin - gapsize,
+                                   px + gapsize + lenghtx / 4,
+                                   ymax + gapsize)
+                subtract_rectangle(cutout_obj,
+                                   px - gapsize - lenghtx / 4,
+                                   ymin - gapsize,
+                                   px + gapsize - lenghtx / 4,
+                                   ymax + gapsize)
+
+            if gaps_u == 4 or gaps_u == 'lr':
+                subtract_rectangle(cutout_obj,
+                                   xmin - gapsize,
+                                   py - gapsize,
+                                   xmax + gapsize,
+                                   py + gapsize)
+
+            if gaps_u == 4 or gaps_u == 'tb':
+                subtract_rectangle(cutout_obj,
+                                   px - gapsize,
+                                   ymin - gapsize,
+                                   px + gapsize,
+                                   ymax + gapsize)
+
+            cutout_obj.plot()
+            self.app.inform.emit("[success]Any-form Cutout operation finished.")
+        elif isinstance(cutout_obj, FlatCAMGerber):
+
+            def geo_init(geo_obj, app_obj):
+                try:
+                    geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2)
+                except Exception as e:
+                    log.debug("TclCommandGeoCutout.execute() --> %s" % str(e))
+                    return 'fail'
+
+                if gaps_u == 8 or gaps_u == '2lr':
+                    geo = substract_rectangle_geo(geo,
+                                       xmin - gapsize,  # botleft_x
+                                       py - gapsize + lenghty / 4,  # botleft_y
+                                       xmax + gapsize,  # topright_x
+                                       py + gapsize + lenghty / 4)  # topright_y
+                    geo = substract_rectangle_geo(geo,
+                                       xmin - gapsize,
+                                       py - gapsize - lenghty / 4,
+                                       xmax + gapsize,
+                                       py + gapsize - lenghty / 4)
+
+                if gaps_u == 8 or gaps_u == '2tb':
+                    geo = substract_rectangle_geo(geo,
+                                       px - gapsize + lenghtx / 4,
+                                       ymin - gapsize,
+                                       px + gapsize + lenghtx / 4,
+                                       ymax + gapsize)
+                    geo = substract_rectangle_geo(geo,
+                                       px - gapsize - lenghtx / 4,
+                                       ymin - gapsize,
+                                       px + gapsize - lenghtx / 4,
+                                       ymax + gapsize)
+
+                if gaps_u == 4 or gaps_u == 'lr':
+                    geo = substract_rectangle_geo(geo,
+                                       xmin - gapsize,
+                                       py - gapsize,
+                                       xmax + gapsize,
+                                       py + gapsize)
+
+                if gaps_u == 4 or gaps_u == 'tb':
+                    geo = substract_rectangle_geo(geo,
+                                       px - gapsize,
+                                       ymin - gapsize,
+                                       px + gapsize,
+                                       ymax + gapsize)
+                geo_obj.solid_geometry = geo
+
+            outname = cutout_obj.options["name"] + "_cutout"
+            self.app.new_object('geometry', outname, geo_init)
+
+            cutout_obj = self.app.collection.get_by_name(outname)
+        else:
+            self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
+            return
+
+
+
+

+ 0 - 1
tclCommands/__init__.py

@@ -12,7 +12,6 @@ import tclCommands.TclCommandAlignDrillGrid
 import tclCommands.TclCommandClearShell
 import tclCommands.TclCommandClearShell
 import tclCommands.TclCommandCncjob
 import tclCommands.TclCommandCncjob
 import tclCommands.TclCommandCutout
 import tclCommands.TclCommandCutout
-import tclCommands.TclCommandCutoutAny
 import tclCommands.TclCommandDelete
 import tclCommands.TclCommandDelete
 import tclCommands.TclCommandDrillcncjob
 import tclCommands.TclCommandDrillcncjob
 import tclCommands.TclCommandExportGcode
 import tclCommands.TclCommandExportGcode

Некоторые файлы не были показаны из-за большого количества измененных файлов