Explorar o código

- whenever a FlatCAM tool is activated, if the notebook side is hidden it will be unhidden
- reactivated the Voronoi classed
- added a new parameter named Offset in the Excellon tool table - work in progress

Marius Stanciu %!s(int64=7) %!d(string=hai) anos
pai
achega
d0641458e4

+ 3 - 1
FlatCAMApp.py

@@ -93,7 +93,7 @@ class App(QtCore.QObject):
 
 
     # Version
     # Version
     version = 8.909
     version = 8.909
-    version_date = "2019/02/12"
+    version_date = "2019/02/13"
     beta = True
     beta = True
 
 
     # current date now
     # current date now
@@ -365,6 +365,7 @@ class App(QtCore.QObject):
             "excellon_startz": self.excellon_defaults_form.excellon_opt_group.estartz_entry,
             "excellon_startz": self.excellon_defaults_form.excellon_opt_group.estartz_entry,
             "excellon_endz": self.excellon_defaults_form.excellon_opt_group.eendz_entry,
             "excellon_endz": self.excellon_defaults_form.excellon_opt_group.eendz_entry,
             "excellon_tooldia": self.excellon_defaults_form.excellon_opt_group.tooldia_entry,
             "excellon_tooldia": self.excellon_defaults_form.excellon_opt_group.tooldia_entry,
+            "excellon_offset": self.excellon_defaults_form.excellon_opt_group.offset_entry,
             "excellon_slot_tooldia": self.excellon_defaults_form.excellon_opt_group.slot_tooldia_entry,
             "excellon_slot_tooldia": self.excellon_defaults_form.excellon_opt_group.slot_tooldia_entry,
             "excellon_gcode_type": self.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio,
             "excellon_gcode_type": self.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio,
 
 
@@ -556,6 +557,7 @@ class App(QtCore.QObject):
             "excellon_toolchangez": 1.0,
             "excellon_toolchangez": 1.0,
             "excellon_toolchangexy": "0.0, 0.0",
             "excellon_toolchangexy": "0.0, 0.0",
             "excellon_tooldia": 0.016,
             "excellon_tooldia": 0.016,
+            "excellon_offset": 0.0,
             "excellon_slot_tooldia": 0.016,
             "excellon_slot_tooldia": 0.016,
             "excellon_startz": None,
             "excellon_startz": None,
             "excellon_endz": 2.0,
             "excellon_endz": 2.0,

+ 8 - 1
FlatCAMEditor.py

@@ -3532,6 +3532,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         self.new_drills = []
         self.new_drills = []
         self.new_tools = {}
         self.new_tools = {}
         self.new_slots = {}
         self.new_slots = {}
+        self.new_tool_offset = {}
 
 
         # dictionary to store the tool_row and diameters in Tool_table
         # dictionary to store the tool_row and diameters in Tool_table
         # it will be updated everytime self.build_ui() is called
         # it will be updated everytime self.build_ui() is called
@@ -3872,7 +3873,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
         horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
         # horizontal_header.setStretchLastSection(True)
         # horizontal_header.setStretchLastSection(True)
 
 
-        self.tools_table_exc.setSortingEnabled(True)
+        # self.tools_table_exc.setSortingEnabled(True)
         # sort by tool diameter
         # sort by tool diameter
         self.tools_table_exc.sortItems(1)
         self.tools_table_exc.sortItems(1)
 
 
@@ -3949,6 +3950,7 @@ class FlatCAMExcEditor(QtCore.QObject):
     def on_tool_delete(self, dia=None):
     def on_tool_delete(self, dia=None):
         self.is_modified = True
         self.is_modified = True
         deleted_tool_dia_list = []
         deleted_tool_dia_list = []
+        deleted_tool_offset_list = []
 
 
         try:
         try:
             if dia is None or dia is False:
             if dia is None or dia is False:
@@ -3984,6 +3986,8 @@ class FlatCAMExcEditor(QtCore.QObject):
             if flag_del:
             if flag_del:
                 for tool_to_be_deleted in flag_del:
                 for tool_to_be_deleted in flag_del:
                     self.tool2tooldia.pop(tool_to_be_deleted, None)
                     self.tool2tooldia.pop(tool_to_be_deleted, None)
+                    self.exc_obj.tool_offset.pop(tool_to_be_deleted, None)
+
                     # delete also the drills from points_edit dict just in case we add the tool again, we don't want to show the
                     # delete also the drills from points_edit dict just in case we add the tool again, we don't want to show the
                     # number of drills from before was deleter
                     # number of drills from before was deleter
                     self.points_edit[deleted_tool_dia] = []
                     self.points_edit[deleted_tool_dia] = []
@@ -4315,6 +4319,8 @@ class FlatCAMExcEditor(QtCore.QObject):
         if self.exc_obj.slots:
         if self.exc_obj.slots:
             self.new_slots = self.exc_obj.slots
             self.new_slots = self.exc_obj.slots
 
 
+        self.new_tool_offset = self.exc_obj.tool_offset
+
         # reset the tool table
         # reset the tool table
         self.tools_table_exc.clear()
         self.tools_table_exc.clear()
         self.tools_table_exc.setHorizontalHeaderLabels(['#', 'Diameter', 'D', 'S'])
         self.tools_table_exc.setHorizontalHeaderLabels(['#', 'Diameter', 'D', 'S'])
@@ -4364,6 +4370,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             excellon_obj.drills = self.new_drills
             excellon_obj.drills = self.new_drills
             excellon_obj.tools = self.new_tools
             excellon_obj.tools = self.new_tools
             excellon_obj.slots = self.new_slots
             excellon_obj.slots = self.new_slots
+            excellon_obj.tool_offset = self.new_tool_offset
             excellon_obj.options['name'] = outname
             excellon_obj.options['name'] = outname
 
 
             try:
             try:

+ 41 - 32
FlatCAMGUI.py

@@ -3296,14 +3296,23 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.cutz_entry = LengthEntry()
         self.cutz_entry = LengthEntry()
         grid2.addWidget(self.cutz_entry, 0, 1)
         grid2.addWidget(self.cutz_entry, 0, 1)
 
 
+        offsetlabel = QtWidgets.QLabel('Offset:')
+        offsetlabel.setToolTip(
+            "Some drill bits (the larger ones) need to drill deeper\n"
+            "to create the desired exit hole diameter due of the tip shape.\n"
+            "The value here can compensate the Cut Z parameter.")
+        grid2.addWidget(offsetlabel, 1, 0)
+        self.offset_entry = LengthEntry()
+        grid2.addWidget(self.offset_entry, 1, 1)
+
         travelzlabel = QtWidgets.QLabel('Travel Z:')
         travelzlabel = QtWidgets.QLabel('Travel Z:')
         travelzlabel.setToolTip(
         travelzlabel.setToolTip(
             "Tool height when travelling\n"
             "Tool height when travelling\n"
             "across the XY plane."
             "across the XY plane."
         )
         )
-        grid2.addWidget(travelzlabel, 1, 0)
+        grid2.addWidget(travelzlabel, 2, 0)
         self.travelz_entry = LengthEntry()
         self.travelz_entry = LengthEntry()
-        grid2.addWidget(self.travelz_entry, 1, 1)
+        grid2.addWidget(self.travelz_entry, 2, 1)
 
 
         # Tool change:
         # Tool change:
         toolchlabel = QtWidgets.QLabel("Tool change:")
         toolchlabel = QtWidgets.QLabel("Tool change:")
@@ -3312,51 +3321,51 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             "in G-Code (Pause for tool change)."
             "in G-Code (Pause for tool change)."
         )
         )
         self.toolchange_cb = FCCheckBox()
         self.toolchange_cb = FCCheckBox()
-        grid2.addWidget(toolchlabel, 2, 0)
-        grid2.addWidget(self.toolchange_cb, 2, 1)
+        grid2.addWidget(toolchlabel, 3, 0)
+        grid2.addWidget(self.toolchange_cb, 3, 1)
 
 
         toolchangezlabel = QtWidgets.QLabel('Toolchange Z:')
         toolchangezlabel = QtWidgets.QLabel('Toolchange Z:')
         toolchangezlabel.setToolTip(
         toolchangezlabel.setToolTip(
             "Toolchange Z position."
             "Toolchange Z position."
         )
         )
-        grid2.addWidget(toolchangezlabel, 3, 0)
+        grid2.addWidget(toolchangezlabel, 4, 0)
         self.toolchangez_entry = LengthEntry()
         self.toolchangez_entry = LengthEntry()
-        grid2.addWidget(self.toolchangez_entry, 3, 1)
+        grid2.addWidget(self.toolchangez_entry, 4, 1)
 
 
         toolchange_xy_label = QtWidgets.QLabel('Toolchange X,Y:')
         toolchange_xy_label = QtWidgets.QLabel('Toolchange X,Y:')
         toolchange_xy_label.setToolTip(
         toolchange_xy_label.setToolTip(
             "Toolchange X,Y position."
             "Toolchange X,Y position."
         )
         )
-        grid2.addWidget(toolchange_xy_label, 4, 0)
+        grid2.addWidget(toolchange_xy_label, 5, 0)
         self.toolchangexy_entry = FCEntry()
         self.toolchangexy_entry = FCEntry()
-        grid2.addWidget(self.toolchangexy_entry, 4, 1)
+        grid2.addWidget(self.toolchangexy_entry, 5, 1)
 
 
         startzlabel = QtWidgets.QLabel('Start move Z:')
         startzlabel = QtWidgets.QLabel('Start move Z:')
         startzlabel.setToolTip(
         startzlabel.setToolTip(
             "Height of the tool just after start.\n"
             "Height of the tool just after start.\n"
             "Delete the value if you don't need this feature."
             "Delete the value if you don't need this feature."
         )
         )
-        grid2.addWidget(startzlabel, 5, 0)
+        grid2.addWidget(startzlabel, 6, 0)
         self.estartz_entry = FloatEntry()
         self.estartz_entry = FloatEntry()
-        grid2.addWidget(self.estartz_entry, 5, 1)
+        grid2.addWidget(self.estartz_entry, 6, 1)
 
 
         endzlabel = QtWidgets.QLabel('End move Z:')
         endzlabel = QtWidgets.QLabel('End move Z:')
         endzlabel.setToolTip(
         endzlabel.setToolTip(
             "Height of the tool after\n"
             "Height of the tool after\n"
             "the last move at the end of the job."
             "the last move at the end of the job."
         )
         )
-        grid2.addWidget(endzlabel, 6, 0)
+        grid2.addWidget(endzlabel, 7, 0)
         self.eendz_entry = LengthEntry()
         self.eendz_entry = LengthEntry()
-        grid2.addWidget(self.eendz_entry, 6, 1)
+        grid2.addWidget(self.eendz_entry, 7, 1)
 
 
         frlabel = QtWidgets.QLabel('Feedrate:')
         frlabel = QtWidgets.QLabel('Feedrate:')
         frlabel.setToolTip(
         frlabel.setToolTip(
             "Tool speed while drilling\n"
             "Tool speed while drilling\n"
             "(in units per minute)."
             "(in units per minute)."
         )
         )
-        grid2.addWidget(frlabel, 7, 0)
+        grid2.addWidget(frlabel, 8, 0)
         self.feedrate_entry = LengthEntry()
         self.feedrate_entry = LengthEntry()
-        grid2.addWidget(self.feedrate_entry, 7, 1)
+        grid2.addWidget(self.feedrate_entry, 8, 1)
 
 
         fr_rapid_label = QtWidgets.QLabel('Feedrate Rapids:')
         fr_rapid_label = QtWidgets.QLabel('Feedrate Rapids:')
         fr_rapid_label.setToolTip(
         fr_rapid_label.setToolTip(
@@ -3364,9 +3373,9 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             "with rapid move\n"
             "with rapid move\n"
             "(in units per minute)."
             "(in units per minute)."
         )
         )
-        grid2.addWidget(fr_rapid_label, 8, 0)
+        grid2.addWidget(fr_rapid_label, 9, 0)
         self.feedrate_rapid_entry = LengthEntry()
         self.feedrate_rapid_entry = LengthEntry()
-        grid2.addWidget(self.feedrate_rapid_entry, 8, 1)
+        grid2.addWidget(self.feedrate_rapid_entry, 9, 1)
 
 
         # Spindle speed
         # Spindle speed
         spdlabel = QtWidgets.QLabel('Spindle speed:')
         spdlabel = QtWidgets.QLabel('Spindle speed:')
@@ -3374,9 +3383,9 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             "Speed of the spindle\n"
             "Speed of the spindle\n"
             "in RPM (optional)"
             "in RPM (optional)"
         )
         )
-        grid2.addWidget(spdlabel, 9, 0)
+        grid2.addWidget(spdlabel, 10, 0)
         self.spindlespeed_entry = IntEntry(allow_empty=True)
         self.spindlespeed_entry = IntEntry(allow_empty=True)
-        grid2.addWidget(self.spindlespeed_entry, 9, 1)
+        grid2.addWidget(self.spindlespeed_entry, 10, 1)
 
 
         # Dwell
         # Dwell
         dwelllabel = QtWidgets.QLabel('Dwell:')
         dwelllabel = QtWidgets.QLabel('Dwell:')
@@ -3390,10 +3399,10 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         )
         )
         self.dwell_cb = FCCheckBox()
         self.dwell_cb = FCCheckBox()
         self.dwelltime_entry = FCEntry()
         self.dwelltime_entry = FCEntry()
-        grid2.addWidget(dwelllabel, 10, 0)
-        grid2.addWidget(self.dwell_cb, 10, 1)
-        grid2.addWidget(dwelltime, 11, 0)
-        grid2.addWidget(self.dwelltime_entry, 11, 1)
+        grid2.addWidget(dwelllabel, 11, 0)
+        grid2.addWidget(self.dwell_cb, 11, 1)
+        grid2.addWidget(dwelltime, 12, 0)
+        grid2.addWidget(self.dwelltime_entry, 12, 1)
 
 
         self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
         self.ois_dwell_exc = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
 
 
@@ -3403,10 +3412,10 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             "The postprocessor file that dictates\n"
             "The postprocessor file that dictates\n"
             "gcode output."
             "gcode output."
         )
         )
-        grid2.addWidget(pp_excellon_label, 12, 0)
+        grid2.addWidget(pp_excellon_label, 13, 0)
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb = FCComboBox()
         self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
         self.pp_excellon_name_cb.setFocusPolicy(Qt.StrongFocus)
-        grid2.addWidget(self.pp_excellon_name_cb, 12, 1)
+        grid2.addWidget(self.pp_excellon_name_cb, 13, 1)
 
 
         # Probe depth
         # Probe depth
         self.pdepth_label = QtWidgets.QLabel("Probe Z depth:")
         self.pdepth_label = QtWidgets.QLabel("Probe Z depth:")
@@ -3414,18 +3423,18 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             "The maximum depth that the probe is allowed\n"
             "The maximum depth that the probe is allowed\n"
             "to probe. Negative value, in current units."
             "to probe. Negative value, in current units."
         )
         )
-        grid2.addWidget(self.pdepth_label, 13, 0)
+        grid2.addWidget(self.pdepth_label, 14, 0)
         self.pdepth_entry = FCEntry()
         self.pdepth_entry = FCEntry()
-        grid2.addWidget(self.pdepth_entry, 13, 1)
+        grid2.addWidget(self.pdepth_entry, 14, 1)
 
 
         # Probe feedrate
         # Probe feedrate
         self.feedrate_probe_label = QtWidgets.QLabel("Feedrate Probe:")
         self.feedrate_probe_label = QtWidgets.QLabel("Feedrate Probe:")
         self.feedrate_probe_label.setToolTip(
         self.feedrate_probe_label.setToolTip(
             "The feedrate used while the probe is probing."
             "The feedrate used while the probe is probing."
         )
         )
-        grid2.addWidget(self.feedrate_probe_label, 14, 0)
+        grid2.addWidget(self.feedrate_probe_label, 15, 0)
         self.feedrate_probe_entry = FCEntry()
         self.feedrate_probe_entry = FCEntry()
-        grid2.addWidget(self.feedrate_probe_entry, 14, 1)
+        grid2.addWidget(self.feedrate_probe_entry, 15, 1)
 
 
         fplungelabel = QtWidgets.QLabel('Fast Plunge:')
         fplungelabel = QtWidgets.QLabel('Fast Plunge:')
         fplungelabel.setToolTip(
         fplungelabel.setToolTip(
@@ -3435,8 +3444,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
             "WARNING: the move is done at Toolchange X,Y coords."
             "WARNING: the move is done at Toolchange X,Y coords."
         )
         )
         self.fplunge_cb = FCCheckBox()
         self.fplunge_cb = FCCheckBox()
-        grid2.addWidget(fplungelabel, 15, 0)
-        grid2.addWidget(self.fplunge_cb, 15, 1)
+        grid2.addWidget(fplungelabel, 16, 0)
+        grid2.addWidget(self.fplunge_cb, 16, 1)
 
 
         #### Choose what to use for Gcode creation: Drills, Slots or Both
         #### Choose what to use for Gcode creation: Drills, Slots or Both
         excellon_gcode_type_label = QtWidgets.QLabel('<b>Gcode:    </b>')
         excellon_gcode_type_label = QtWidgets.QLabel('<b>Gcode:    </b>')
@@ -3449,8 +3458,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
         self.excellon_gcode_type_radio = RadioSet([{'label': 'Drills', 'value': 'drills'},
                                           {'label': 'Slots', 'value': 'slots'},
                                           {'label': 'Slots', 'value': 'slots'},
                                           {'label': 'Both', 'value': 'both'}])
                                           {'label': 'Both', 'value': 'both'}])
-        grid2.addWidget(excellon_gcode_type_label, 16, 0)
-        grid2.addWidget(self.excellon_gcode_type_radio, 16, 1)
+        grid2.addWidget(excellon_gcode_type_label, 17, 0)
+        grid2.addWidget(self.excellon_gcode_type_radio, 17, 1)
 
 
         # until I decide to implement this feature those remain disabled
         # until I decide to implement this feature those remain disabled
         excellon_gcode_type_label.hide()
         excellon_gcode_type_label.hide()

+ 69 - 0
FlatCAMObj.py

@@ -865,6 +865,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         # TODO: Document this.
         # TODO: Document this.
         self.tool_cbs = {}
         self.tool_cbs = {}
 
 
+        # dict to hold the tool number as key and tool offset as value
+        self.tool_offset ={}
+
         # Attributes to be included in serialization
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # Always append to it because it carries contents
         # from predecessors.
         # from predecessors.
@@ -1058,6 +1061,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     def build_ui(self):
     def build_ui(self):
         FlatCAMObj.build_ui(self)
         FlatCAMObj.build_ui(self)
 
 
+        try:
+            # if connected, disconnect the signal from the slot on item_changed as it creates issues
+            self.ui.tools_table.itemChanged.disconnect()
+        except:
+            pass
+
         n = len(self.tools)
         n = len(self.tools)
         # we have (n+2) rows because there are 'n' tools, each a row, plus the last 2 rows for totals.
         # we have (n+2) rows because there are 'n' tools, each a row, plus the last 2 rows for totals.
         self.ui.tools_table.setRowCount(n + 2)
         self.ui.tools_table.setRowCount(n + 2)
@@ -1116,9 +1125,20 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                 slot_count = QtWidgets.QTableWidgetItem('')
                 slot_count = QtWidgets.QTableWidgetItem('')
             slot_count.setFlags(QtCore.Qt.ItemIsEnabled)
             slot_count.setFlags(QtCore.Qt.ItemIsEnabled)
 
 
+            try:
+                if self.units == 'MM':
+                    t_offset = self.tool_offset[float('%.2f' % float(self.tools[tool_no]['C']))]
+                else:
+                    t_offset = self.tool_offset[float('%.3f' % float(self.tools[tool_no]['C']))]
+            except KeyError:
+                    t_offset = self.app.defaults['excellon_offset']
+            tool_offset_item = QtWidgets.QTableWidgetItem('%s' % str(t_offset))
+
             self.ui.tools_table.setItem(self.tool_row, 1, dia)  # Diameter
             self.ui.tools_table.setItem(self.tool_row, 1, dia)  # Diameter
             self.ui.tools_table.setItem(self.tool_row, 2, drill_count)  # Number of drills per tool
             self.ui.tools_table.setItem(self.tool_row, 2, drill_count)  # Number of drills per tool
             self.ui.tools_table.setItem(self.tool_row, 3, slot_count)  # Number of drills per tool
             self.ui.tools_table.setItem(self.tool_row, 3, slot_count)  # Number of drills per tool
+            self.ui.tools_table.setItem(self.tool_row, 4, tool_offset_item)  # Tool offset
+
             self.tool_row += 1
             self.tool_row += 1
 
 
         # add a last row with the Total number of drills
         # add a last row with the Total number of drills
@@ -1210,6 +1230,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             self.ui.slot_tooldia_entry.show()
             self.ui.slot_tooldia_entry.show()
             self.ui.generate_milling_slots_button.show()
             self.ui.generate_milling_slots_button.show()
 
 
+        # we reactivate the signals after the after the tool adding as we don't need to see the tool been populated
+        self.ui.tools_table.itemChanged.connect(self.on_tool_offset_edit)
+
     def set_ui(self, ui):
     def set_ui(self, ui):
         """
         """
         Configures the user interface for this object.
         Configures the user interface for this object.
@@ -1254,6 +1277,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         # Fill form fields
         # Fill form fields
         self.to_form()
         self.to_form()
 
 
+        # initialize the dict that holds the tools offset
+        t_default_offset = self.app.defaults["excellon_offset"]
+        if not self.tool_offset:
+            for value in self.tools.values():
+                if self.units == 'MM':
+                    dia = float('%.2f' % float(value['C']))
+                else:
+                    dia = float('%.3f' % float(value['C']))
+                self.tool_offset[dia] = t_default_offset
+
         assert isinstance(self.ui, ExcellonObjectUI), \
         assert isinstance(self.ui, ExcellonObjectUI), \
             "Expected a ExcellonObjectUI, got %s" % type(self.ui)
             "Expected a ExcellonObjectUI, got %s" % type(self.ui)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
         self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
@@ -1264,6 +1297,42 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
 
 
         self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed)
         self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed)
 
 
+    def on_tool_offset_edit(self):
+        # if connected, disconnect the signal from the slot on item_changed as it creates issues
+        self.ui.tools_table.itemChanged.disconnect()
+        # self.tools_table_exc.selectionModel().currentChanged.disconnect()
+
+        self.is_modified = True
+
+        row_of_item_changed = self.ui.tools_table.currentRow()
+        if self.units == 'MM':
+            dia = float('%.2f' % float(self.ui.tools_table.item(row_of_item_changed, 1).text()))
+        else:
+            dia = float('%.3f' % float(self.ui.tools_table.item(row_of_item_changed, 1).text()))
+
+        current_table_offset_edited = None
+        if self.ui.tools_table.currentItem() is not None:
+            try:
+                current_table_offset_edited = float(self.ui.tools_table.currentItem().text())
+            except ValueError:
+                # try to convert comma to decimal point. if it's still not working error message and return
+                try:
+                    current_table_offset_edited = float(self.ui.tools_table.currentItem().text().replace(',', '.'))
+                    self.ui.tools_table.currentItem().setText(
+                        self.ui.tools_table.currentItem().text().replace(',', '.'))
+                except ValueError:
+                    self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
+                                         "use a number.")
+                    self.ui.tools_table.currentItem().setText(str(self.tool_offset[dia]))
+                    return
+
+        self.tool_offset[dia] = current_table_offset_edited
+
+        print(self.tool_offset)
+
+        # we reactivate the signals after the after the tool editing
+        self.ui.tools_table.itemChanged.connect(self.on_tool_offset_edit)
+
     def get_selected_tools_list(self):
     def get_selected_tools_list(self):
         """
         """
         Returns the keys to the self.tools dictionary corresponding
         Returns the keys to the self.tools dictionary corresponding

+ 6 - 2
ObjectUI.py

@@ -419,8 +419,8 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_table = FCTable()
         self.tools_table = FCTable()
         self.tools_box.addWidget(self.tools_table)
         self.tools_box.addWidget(self.tools_table)
 
 
-        self.tools_table.setColumnCount(4)
-        self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', 'D', 'S'])
+        self.tools_table.setColumnCount(5)
+        self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', 'D', 'S', 'Offset'])
         self.tools_table.setSortingEnabled(False)
         self.tools_table.setSortingEnabled(False)
 
 
         self.tools_table.horizontalHeaderItem(0).setToolTip(
         self.tools_table.horizontalHeaderItem(0).setToolTip(
@@ -436,6 +436,10 @@ class ExcellonObjectUI(ObjectUI):
         self.tools_table.horizontalHeaderItem(3).setToolTip(
         self.tools_table.horizontalHeaderItem(3).setToolTip(
             "The number of Slot holes. Holes that are created by\n"
             "The number of Slot holes. Holes that are created by\n"
             "milling them with an endmill bit.")
             "milling them with an endmill bit.")
+        self.tools_table.horizontalHeaderItem(4).setToolTip(
+            "Some drill bits (the larger ones) need to drill deeper\n"
+            "to create the desired exit hole diameter due of the tip shape.\n"
+            "The value here can compensate the Cut Z parameter.")
 
 
         self.empty_label = QtWidgets.QLabel('')
         self.empty_label = QtWidgets.QLabel('')
         self.tools_box.addWidget(self.empty_label)
         self.tools_box.addWidget(self.empty_label)

+ 6 - 0
README.md

@@ -9,6 +9,12 @@ CAD program, and create G-Code for Isolation routing.
 
 
 =================================================
 =================================================
 
 
+12.02.2019
+
+- whenever a FlatCAM tool is activated, if the notebook side is hidden it will be unhidden
+- reactivated the Voronoi classed
+- added a new parameter named Offset in the Excellon tool table - work in progress
+
 10.02.2019
 10.02.2019
 
 
 - the SELECTED type of messages are no longer printed to shell from 2 reasons: first, too much spam and second, issue with displaying html
 - the SELECTED type of messages are no longer printed to shell from 2 reasons: first, too much spam and second, issue with displaying html

+ 208 - 200
camlib.py

@@ -43,6 +43,8 @@ from rasterio.features import shapes
 
 
 from xml.dom.minidom import parseString as parse_xml_string
 from xml.dom.minidom import parseString as parse_xml_string
 
 
+from scipy.spatial import KDTree, Delaunay
+
 from ParseSVG import *
 from ParseSVG import *
 from ParseDXF import *
 from ParseDXF import *
 
 
@@ -6491,206 +6493,212 @@ def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
     return ret_val
     return ret_val
 
 
 
 
-# def voronoi(P):
-#     """
-#     Returns a list of all edges of the voronoi diagram for the given input points.
-#     """
-#     delauny = Delaunay(P)
-#     triangles = delauny.points[delauny.vertices]
-#
-#     circum_centers = np.array([triangle_csc(tri) for tri in triangles])
-#     long_lines_endpoints = []
-#
-#     lineIndices = []
-#     for i, triangle in enumerate(triangles):
-#         circum_center = circum_centers[i]
-#         for j, neighbor in enumerate(delauny.neighbors[i]):
-#             if neighbor != -1:
-#                 lineIndices.append((i, neighbor))
-#             else:
-#                 ps = triangle[(j+1)%3] - triangle[(j-1)%3]
-#                 ps = np.array((ps[1], -ps[0]))
-#
-#                 middle = (triangle[(j+1)%3] + triangle[(j-1)%3]) * 0.5
-#                 di = middle - triangle[j]
-#
-#                 ps /= np.linalg.norm(ps)
-#                 di /= np.linalg.norm(di)
-#
-#                 if np.dot(di, ps) < 0.0:
-#                     ps *= -1000.0
-#                 else:
-#                     ps *= 1000.0
-#
-#                 long_lines_endpoints.append(circum_center + ps)
-#                 lineIndices.append((i, len(circum_centers) + len(long_lines_endpoints)-1))
-#
-#     vertices = np.vstack((circum_centers, long_lines_endpoints))
-#
-#     # filter out any duplicate lines
-#     lineIndicesSorted = np.sort(lineIndices) # make (1,2) and (2,1) both (1,2)
-#     lineIndicesTupled = [tuple(row) for row in lineIndicesSorted]
-#     lineIndicesUnique = np.unique(lineIndicesTupled)
-#
-#     return vertices, lineIndicesUnique
-#
-#
-# def triangle_csc(pts):
-#     rows, cols = pts.shape
-#
-#     A = np.bmat([[2 * np.dot(pts, pts.T), np.ones((rows, 1))],
-#                  [np.ones((1, rows)), np.zeros((1, 1))]])
-#
-#     b = np.hstack((np.sum(pts * pts, axis=1), np.ones((1))))
-#     x = np.linalg.solve(A,b)
-#     bary_coords = x[:-1]
-#     return np.sum(pts * np.tile(bary_coords.reshape((pts.shape[0], 1)), (1, pts.shape[1])), axis=0)
-#
-#
-# def voronoi_cell_lines(points, vertices, lineIndices):
-#     """
-#     Returns a mapping from a voronoi cell to its edges.
-#
-#     :param points: shape (m,2)
-#     :param vertices: shape (n,2)
-#     :param lineIndices: shape (o,2)
-#     :rtype: dict point index -> list of shape (n,2) with vertex indices
-#     """
-#     kd = KDTree(points)
-#
-#     cells = collections.defaultdict(list)
-#     for i1, i2 in lineIndices:
-#         v1, v2 = vertices[i1], vertices[i2]
-#         mid = (v1+v2)/2
-#         _, (p1Idx, p2Idx) = kd.query(mid, 2)
-#         cells[p1Idx].append((i1, i2))
-#         cells[p2Idx].append((i1, i2))
-#
-#     return cells
-#
-#
-# def voronoi_edges2polygons(cells):
-#     """
-#     Transforms cell edges into polygons.
-#
-#     :param cells: as returned from voronoi_cell_lines
-#     :rtype: dict point index -> list of vertex indices which form a polygon
-#     """
-#
-#     # first, close the outer cells
-#     for pIdx, lineIndices_ in cells.items():
-#         dangling_lines = []
-#         for i1, i2 in lineIndices_:
-#             connections = filter(lambda (i1_, i2_): (i1, i2) != (i1_, i2_) and (i1 == i1_ or i1 == i2_ or i2 == i1_ or i2 == i2_), lineIndices_)
-#             assert 1 <= len(connections) <= 2
-#             if len(connections) == 1:
-#                 dangling_lines.append((i1, i2))
-#         assert len(dangling_lines) in [0, 2]
-#         if len(dangling_lines) == 2:
-#             (i11, i12), (i21, i22) = dangling_lines
-#
-#             # determine which line ends are unconnected
-#             connected = filter(lambda (i1,i2): (i1,i2) != (i11,i12) and (i1 == i11 or i2 == i11), lineIndices_)
-#             i11Unconnected = len(connected) == 0
-#
-#             connected = filter(lambda (i1,i2): (i1,i2) != (i21,i22) and (i1 == i21 or i2 == i21), lineIndices_)
-#             i21Unconnected = len(connected) == 0
-#
-#             startIdx = i11 if i11Unconnected else i12
-#             endIdx = i21 if i21Unconnected else i22
-#
-#             cells[pIdx].append((startIdx, endIdx))
-#
-#     # then, form polygons by storing vertex indices in (counter-)clockwise order
-#     polys = dict()
-#     for pIdx, lineIndices_ in cells.items():
-#         # get a directed graph which contains both directions and arbitrarily follow one of both
-#         directedGraph = lineIndices_ + [(i2, i1) for (i1, i2) in lineIndices_]
-#         directedGraphMap = collections.defaultdict(list)
-#         for (i1, i2) in directedGraph:
-#             directedGraphMap[i1].append(i2)
-#         orderedEdges = []
-#         currentEdge = directedGraph[0]
-#         while len(orderedEdges) < len(lineIndices_):
-#             i1 = currentEdge[1]
-#             i2 = directedGraphMap[i1][0] if directedGraphMap[i1][0] != currentEdge[0] else directedGraphMap[i1][1]
-#             nextEdge = (i1, i2)
-#             orderedEdges.append(nextEdge)
-#             currentEdge = nextEdge
-#
-#         polys[pIdx] = [i1 for (i1, i2) in orderedEdges]
-#
-#     return polys
-#
-#
-# def voronoi_polygons(points):
-#     """
-#     Returns the voronoi polygon for each input point.
-#
-#     :param points: shape (n,2)
-#     :rtype: list of n polygons where each polygon is an array of vertices
-#     """
-#     vertices, lineIndices = voronoi(points)
-#     cells = voronoi_cell_lines(points, vertices, lineIndices)
-#     polys = voronoi_edges2polygons(cells)
-#     polylist = []
-#     for i in xrange(len(points)):
-#         poly = vertices[np.asarray(polys[i])]
-#         polylist.append(poly)
-#     return polylist
-#
-#
-# class Zprofile:
-#     def __init__(self):
-#
-#         # data contains lists of [x, y, z]
-#         self.data = []
-#
-#         # Computed voronoi polygons (shapely)
-#         self.polygons = []
-#         pass
-#
-#     def plot_polygons(self):
-#         axes = plt.subplot(1, 1, 1)
-#
-#         plt.axis([-0.05, 1.05, -0.05, 1.05])
-#
-#         for poly in self.polygons:
-#             p = PolygonPatch(poly, facecolor=np.random.rand(3, 1), alpha=0.3)
-#             axes.add_patch(p)
-#
-#     def init_from_csv(self, filename):
-#         pass
-#
-#     def init_from_string(self, zpstring):
-#         pass
-#
-#     def init_from_list(self, zplist):
-#         self.data = zplist
-#
-#     def generate_polygons(self):
-#         self.polygons = [Polygon(p) for p in voronoi_polygons(array([[x[0], x[1]] for x in self.data]))]
-#
-#     def normalize(self, origin):
-#         pass
-#
-#     def paste(self, path):
-#         """
-#         Return a list of dictionaries containing the parts of the original
-#         path and their z-axis offset.
-#         """
-#
-#         # At most one region/polygon will contain the path
-#         containing = [i for i in range(len(self.polygons)) if self.polygons[i].contains(path)]
-#
-#         if len(containing) > 0:
-#             return [{"path": path, "z": self.data[containing[0]][2]}]
-#
-#         # All region indexes that intersect with the path
-#         crossing = [i for i in range(len(self.polygons)) if self.polygons[i].intersects(path)]
-#
-#         return [{"path": path.intersection(self.polygons[i]),
-#                  "z": self.data[i][2]} for i in crossing]
+def voronoi(P):
+    """
+    Returns a list of all edges of the voronoi diagram for the given input points.
+    """
+    delauny = Delaunay(P)
+    triangles = delauny.points[delauny.vertices]
+
+    circum_centers = np.array([triangle_csc(tri) for tri in triangles])
+    long_lines_endpoints = []
+
+    lineIndices = []
+    for i, triangle in enumerate(triangles):
+        circum_center = circum_centers[i]
+        for j, neighbor in enumerate(delauny.neighbors[i]):
+            if neighbor != -1:
+                lineIndices.append((i, neighbor))
+            else:
+                ps = triangle[(j+1)%3] - triangle[(j-1)%3]
+                ps = np.array((ps[1], -ps[0]))
+
+                middle = (triangle[(j+1)%3] + triangle[(j-1)%3]) * 0.5
+                di = middle - triangle[j]
+
+                ps /= np.linalg.norm(ps)
+                di /= np.linalg.norm(di)
+
+                if np.dot(di, ps) < 0.0:
+                    ps *= -1000.0
+                else:
+                    ps *= 1000.0
+
+                long_lines_endpoints.append(circum_center + ps)
+                lineIndices.append((i, len(circum_centers) + len(long_lines_endpoints)-1))
+
+    vertices = np.vstack((circum_centers, long_lines_endpoints))
+
+    # filter out any duplicate lines
+    lineIndicesSorted = np.sort(lineIndices) # make (1,2) and (2,1) both (1,2)
+    lineIndicesTupled = [tuple(row) for row in lineIndicesSorted]
+    lineIndicesUnique = np.unique(lineIndicesTupled)
+
+    return vertices, lineIndicesUnique
+
+
+def triangle_csc(pts):
+    rows, cols = pts.shape
+
+    A = np.bmat([[2 * np.dot(pts, pts.T), np.ones((rows, 1))],
+                 [np.ones((1, rows)), np.zeros((1, 1))]])
+
+    b = np.hstack((np.sum(pts * pts, axis=1), np.ones((1))))
+    x = np.linalg.solve(A,b)
+    bary_coords = x[:-1]
+    return np.sum(pts * np.tile(bary_coords.reshape((pts.shape[0], 1)), (1, pts.shape[1])), axis=0)
+
+
+def voronoi_cell_lines(points, vertices, lineIndices):
+    """
+    Returns a mapping from a voronoi cell to its edges.
+
+    :param points: shape (m,2)
+    :param vertices: shape (n,2)
+    :param lineIndices: shape (o,2)
+    :rtype: dict point index -> list of shape (n,2) with vertex indices
+    """
+    kd = KDTree(points)
+
+    cells = collections.defaultdict(list)
+    for i1, i2 in lineIndices:
+        v1, v2 = vertices[i1], vertices[i2]
+        mid = (v1+v2)/2
+        _, (p1Idx, p2Idx) = kd.query(mid, 2)
+        cells[p1Idx].append((i1, i2))
+        cells[p2Idx].append((i1, i2))
+
+    return cells
+
+
+def voronoi_edges2polygons(cells):
+    """
+    Transforms cell edges into polygons.
+
+    :param cells: as returned from voronoi_cell_lines
+    :rtype: dict point index -> list of vertex indices which form a polygon
+    """
+
+    # first, close the outer cells
+    for pIdx, lineIndices_ in cells.items():
+        dangling_lines = []
+        for i1, i2 in lineIndices_:
+            p = (i1, i2)
+            connections = filter(lambda k: p != k and (p[0] == k[0] or p[0] == k[1] or p[1] == k[0] or p[1] == k[1]), lineIndices_)
+            # connections = filter(lambda (i1_, i2_): (i1, i2) != (i1_, i2_) and (i1 == i1_ or i1 == i2_ or i2 == i1_ or i2 == i2_), lineIndices_)
+            assert 1 <= len(connections) <= 2
+            if len(connections) == 1:
+                dangling_lines.append((i1, i2))
+        assert len(dangling_lines) in [0, 2]
+        if len(dangling_lines) == 2:
+            (i11, i12), (i21, i22) = dangling_lines
+            s = (i11, i12)
+            t = (i21, i22)
+
+            # determine which line ends are unconnected
+            connected = filter(lambda k: k != s and (k[0] == s[0] or k[1] == s[0]), lineIndices_)
+            # connected = filter(lambda (i1,i2): (i1,i2) != (i11,i12) and (i1 == i11 or i2 == i11), lineIndices_)
+            i11Unconnected = len(connected) == 0
+
+            connected = filter(lambda k: k != t and (k[0] == t[0] or k[1] == t[0]), lineIndices_)
+            # connected = filter(lambda (i1,i2): (i1,i2) != (i21,i22) and (i1 == i21 or i2 == i21), lineIndices_)
+            i21Unconnected = len(connected) == 0
+
+            startIdx = i11 if i11Unconnected else i12
+            endIdx = i21 if i21Unconnected else i22
+
+            cells[pIdx].append((startIdx, endIdx))
+
+    # then, form polygons by storing vertex indices in (counter-)clockwise order
+    polys = dict()
+    for pIdx, lineIndices_ in cells.items():
+        # get a directed graph which contains both directions and arbitrarily follow one of both
+        directedGraph = lineIndices_ + [(i2, i1) for (i1, i2) in lineIndices_]
+        directedGraphMap = collections.defaultdict(list)
+        for (i1, i2) in directedGraph:
+            directedGraphMap[i1].append(i2)
+        orderedEdges = []
+        currentEdge = directedGraph[0]
+        while len(orderedEdges) < len(lineIndices_):
+            i1 = currentEdge[1]
+            i2 = directedGraphMap[i1][0] if directedGraphMap[i1][0] != currentEdge[0] else directedGraphMap[i1][1]
+            nextEdge = (i1, i2)
+            orderedEdges.append(nextEdge)
+            currentEdge = nextEdge
+
+        polys[pIdx] = [i1 for (i1, i2) in orderedEdges]
+
+    return polys
+
+
+def voronoi_polygons(points):
+    """
+    Returns the voronoi polygon for each input point.
+
+    :param points: shape (n,2)
+    :rtype: list of n polygons where each polygon is an array of vertices
+    """
+    vertices, lineIndices = voronoi(points)
+    cells = voronoi_cell_lines(points, vertices, lineIndices)
+    polys = voronoi_edges2polygons(cells)
+    polylist = []
+    for i in range(len(points)):
+        poly = vertices[np.asarray(polys[i])]
+        polylist.append(poly)
+    return polylist
+
+
+class Zprofile:
+    def __init__(self):
+
+        # data contains lists of [x, y, z]
+        self.data = []
+
+        # Computed voronoi polygons (shapely)
+        self.polygons = []
+        pass
+
+    # def plot_polygons(self):
+    #     axes = plt.subplot(1, 1, 1)
+    #
+    #     plt.axis([-0.05, 1.05, -0.05, 1.05])
+    #
+    #     for poly in self.polygons:
+    #         p = PolygonPatch(poly, facecolor=np.random.rand(3, 1), alpha=0.3)
+    #         axes.add_patch(p)
+
+    def init_from_csv(self, filename):
+        pass
+
+    def init_from_string(self, zpstring):
+        pass
+
+    def init_from_list(self, zplist):
+        self.data = zplist
+
+    def generate_polygons(self):
+        self.polygons = [Polygon(p) for p in voronoi_polygons(array([[x[0], x[1]] for x in self.data]))]
+
+    def normalize(self, origin):
+        pass
+
+    def paste(self, path):
+        """
+        Return a list of dictionaries containing the parts of the original
+        path and their z-axis offset.
+        """
+
+        # At most one region/polygon will contain the path
+        containing = [i for i in range(len(self.polygons)) if self.polygons[i].contains(path)]
+
+        if len(containing) > 0:
+            return [{"path": path, "z": self.data[containing[0]][2]}]
+
+        # All region indexes that intersect with the path
+        crossing = [i for i in range(len(self.polygons)) if self.polygons[i].intersects(path)]
+
+        return [{"path": path.intersection(self.polygons[i]),
+                 "z": self.data[i][2]} for i in crossing]
 
 
 
 
 def autolist(obj):
 def autolist(obj):

+ 5 - 0
flatcamTools/ToolCalculators.py

@@ -220,6 +220,11 @@ class ToolCalculator(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Calc. Tool")
         self.app.ui.notebook.setTabText(2, "Calc. Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):

+ 5 - 0
flatcamTools/ToolCutOut.py

@@ -196,6 +196,11 @@ class ToolCutOut(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Cutout Tool")
         self.app.ui.notebook.setTabText(2, "Cutout Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):

+ 5 - 0
flatcamTools/ToolDblSided.py

@@ -259,6 +259,11 @@ class DblSidedTool(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "2-Sided Tool")
         self.app.ui.notebook.setTabText(2, "2-Sided Tool")
 
 
     def set_tool_ui(self):
     def set_tool_ui(self):

+ 5 - 0
flatcamTools/ToolFilm.py

@@ -161,6 +161,11 @@ class Film(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Film Tool")
         self.app.ui.notebook.setTabText(2, "Film Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):

+ 5 - 0
flatcamTools/ToolImage.py

@@ -129,6 +129,11 @@ class ToolImage(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Image Tool")
         self.app.ui.notebook.setTabText(2, "Image Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):

+ 5 - 0
flatcamTools/ToolNonCopperClear.py

@@ -243,6 +243,11 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.build_ui()
         self.build_ui()
         self.app.ui.notebook.setTabText(2, "NCC Tool")
         self.app.ui.notebook.setTabText(2, "NCC Tool")
 
 

+ 5 - 0
flatcamTools/ToolPaint.py

@@ -299,6 +299,11 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Paint Tool")
         self.app.ui.notebook.setTabText(2, "Paint Tool")
 
 
     def on_radio_selection(self):
     def on_radio_selection(self):

+ 5 - 0
flatcamTools/ToolPanelize.py

@@ -184,6 +184,11 @@ class Panelize(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Panel. Tool")
         self.app.ui.notebook.setTabText(2, "Panel. Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):

+ 5 - 0
flatcamTools/ToolProperties.py

@@ -47,6 +47,11 @@ class Properties(FlatCAMTool):
         if self.app.tool_tab_locked is True:
         if self.app.tool_tab_locked is True:
             return
             return
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.properties()
         self.properties()
 
 

+ 5 - 0
flatcamTools/ToolTransform.py

@@ -360,6 +360,11 @@ class ToolTransform(FlatCAMTool):
 
 
         FlatCAMTool.run(self)
         FlatCAMTool.run(self)
         self.set_tool_ui()
         self.set_tool_ui()
+
+        # if the splitter us hidden, display it
+        if self.app.ui.splitter.sizes()[0] == 0:
+            self.app.ui.splitter.setSizes([1, 1])
+
         self.app.ui.notebook.setTabText(2, "Transform Tool")
         self.app.ui.notebook.setTabText(2, "Transform Tool")
 
 
     def install(self, icon=None, separator=None, **kwargs):
     def install(self, icon=None, separator=None, **kwargs):