Selaa lähdekoodia

- changed the data structure for the Excellon object; modified the Excellon parser and the Excellon object class
- fixed partially the Excellon Editor to work with the new data structure
- fixed Excellon export to work with the new data structure
- fixed all transformations in the Excellon object attributes; still need to fix the App Tools that creates or use Exellon objects

Marius Stanciu 5 vuotta sitten
vanhempi
commit
949c265378

+ 7 - 0
CHANGELOG.md

@@ -7,6 +7,13 @@ CHANGELOG for FlatCAM beta
 
 =================================================
 
+16.06.2020
+
+- changed the data structure for the Excellon object; modified the Excellon parser and the Excellon object class
+- fixed partially the Excellon Editor to work with the new data structure
+- fixed Excellon export to work with the new data structure
+- fixed all transformations in the Excellon object attributes; still need to fix the App Tools that creates or use Exellon objects
+
 15.06.2020
 
 - in Paint Tool and NCC Tool updated the way the selected tools were processed and made sure that the Tools Table rows are counted only once in the processing

+ 74 - 62
appEditors/AppExcEditor.py

@@ -390,8 +390,7 @@ class FCSlot(FCShapeTool):
             item = self.draw_app.tools_table_exc.item((self.draw_app.last_tool_selected - 1), 1)
             self.draw_app.tools_table_exc.setCurrentItem(item)
         except KeyError:
-            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' %
-                                          _("To add a slot first select a tool"))
+            self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % _("To add a slot first select a tool"))
             self.draw_app.select_tool("drill_select")
             return
 
@@ -2059,9 +2058,7 @@ class AppExcEditor(QtCore.QObject):
 
         self.sorted_diameters = []
 
-        self.new_drills = []
         self.new_tools = {}
-        self.new_slots = []
 
         # dictionary to store the tool_row and diameters in Tool_table
         # it will be updated everytime self.build_ui() is called
@@ -2261,30 +2258,33 @@ class AppExcEditor(QtCore.QObject):
         self.tool2tooldia.clear()
 
         # build the self.points_edit dict {dimaters: [point_list]}
-        for drill in self.exc_obj.drills:
-            if drill['tool'] in self.exc_obj.tools:
-                tool_dia = float('%.*f' % (self.decimals, self.exc_obj.tools[drill['tool']]['C']))
+        for tool, tool_dict in self.exc_obj.tools.items():
+            tool_dia = float('%.*f' % (self.decimals, self.exc_obj.tools[tool]['tooldia']))
 
-                try:
-                    self.points_edit[tool_dia].append(drill['point'])
-                except KeyError:
-                    self.points_edit[tool_dia] = [drill['point']]
+            if 'drills' in tool_dict and tool_dict['drills']:
+                for drill in tool_dict['drills']:
+                    try:
+                        self.points_edit[tool_dia].append(drill)
+                    except KeyError:
+                        self.points_edit[tool_dia] = [drill]
 
         # build the self.slot_points_edit dict {dimaters: {"start": Point, "stop": Point}}
-        for slot in self.exc_obj.slots:
-            if slot['tool'] in self.exc_obj.tools:
-                tool_dia = float('%.*f' % (self.decimals, self.exc_obj.tools[slot['tool']]['C']))
+        for tool, tool_dict in self.exc_obj.tools.items():
+            tool_dia = float('%.*f' % (self.decimals, self.exc_obj.tools[tool]['tooldia']))
+
+            if 'slots' in tool_dict and tool_dict['slots']:
+                for slot in tool_dict['slots']:
+                    try:
+                        self.slot_points_edit[tool_dia].append({
+                            "start": slot[0],
+                            "stop": slot[1]
+                        })
+                    except KeyError:
+                        self.slot_points_edit[tool_dia] = [{
+                            "start": slot[0],
+                            "stop": slot[1]
+                        }]
 
-                try:
-                    self.slot_points_edit[tool_dia].append({
-                        "start": slot["start"],
-                        "stop": slot["stop"]
-                    })
-                except KeyError:
-                    self.slot_points_edit[tool_dia] = [{
-                        "start": slot["start"],
-                        "stop": slot["stop"]
-                    }]
 
         # update the olddia_newdia dict to make sure we have an updated state of the tool_table
         for key in self.points_edit:
@@ -2309,7 +2309,7 @@ class AppExcEditor(QtCore.QObject):
             # Excellon file has no tool diameter information. In this case do not order the diameter in the table
             # but use the real order found in the exc_obj.tools
             for k, v in self.exc_obj.tools.items():
-                tool_dia = float('%.*f' % (self.decimals, v['C']))
+                tool_dia = float('%.*f' % (self.decimals, v['tooldia']))
                 self.tool2tooldia[int(k)] = tool_dia
 
         # Init appGUI
@@ -2385,21 +2385,27 @@ class AppExcEditor(QtCore.QObject):
 
             self.tot_drill_cnt += drill_cnt
 
-            try:
-                # Find no of slots for the current tool
-                for slot in self.slot_points_edit:
-                    if slot['tool'] == tool_no:
-                        slot_cnt += 1
-
-                self.tot_slot_cnt += slot_cnt
-            except AttributeError:
-                # log.debug("No slots in the Excellon file")
-                # Find no of slots for the current tool
-                for tool_dia in self.slot_points_edit:
-                    if float(tool_dia) == tool_no:
-                        slot_cnt = len(self.slot_points_edit[tool_dia])
+            # try:
+            #     # Find no of slots for the current tool
+            #     for slot in self.slot_points_edit:
+            #         if float(slot) == tool_no:
+            #             slot_cnt += 1
+            #
+            #     self.tot_slot_cnt += slot_cnt
+            # except AttributeError:
+            #     # log.debug("No slots in the Excellon file")
+            #     # Find no of slots for the current tool
+            #     for tool_dia in self.slot_points_edit:
+            #         if float(tool_dia) == tool_no:
+            #             slot_cnt = len(self.slot_points_edit[tool_dia])
+            #
+            #     self.tot_slot_cnt += slot_cnt
+
+            for tool_dia in self.slot_points_edit:
+                if float(tool_dia) == tool_no:
+                    slot_cnt = len(self.slot_points_edit[tool_dia])
 
-                self.tot_slot_cnt += slot_cnt
+            self.tot_slot_cnt += slot_cnt
 
             idd = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
             idd.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
@@ -3141,6 +3147,10 @@ class AppExcEditor(QtCore.QObject):
                     poly = x.geo
                     poly = poly.buffer(-radius)
 
+                    if not poly.is_valid or poly.is_empty:
+                        print("Polygon not valid: %s" % str(poly.wkt))
+                        continue
+
                     xmin, ymin, xmax, ymax = poly.bounds
                     line_one = LineString([(xmin, ymin), (xmax, ymax)]).intersection(poly).length
                     line_two = LineString([(xmin, ymax), (xmax, ymin)]).intersection(poly).length
@@ -3197,59 +3207,61 @@ class AppExcEditor(QtCore.QObject):
             current_tool += 1
 
             # create the self.tools for the new Excellon object (the one with edited content)
-            name = str(current_tool)
-            spec = {"C": float(tool_dia[0])}
-            self.new_tools[name] = spec
+            if current_tool not in self.new_tools:
+                self.new_tools[current_tool] = {}
+            self.new_tools[current_tool]['tooldia'] = float(tool_dia[0])
 
             # add in self.tools the 'solid_geometry' key, the value (a list) is populated below
-            self.new_tools[name]['solid_geometry'] = []
+            self.new_tools[current_tool]['solid_geometry'] = []
 
             # create the self.drills for the new Excellon object (the one with edited content)
             for point in tool_dia[1]:
-                self.new_drills.append(
-                    {
-                        'point': Point(point),
-                        'tool': str(current_tool)
-                    }
-                )
+                try:
+                    self.new_tools[current_tool]['drills'].append(Point(point))
+                except KeyError:
+                    self.new_tools[current_tool]['drills'] = [Point(point)]
+
                 # repopulate the 'solid_geometry' for each tool
                 poly = Point(point).buffer(float(tool_dia[0]) / 2.0, int(int(exc_obj.geo_steps_per_circle) / 4))
-                self.new_tools[name]['solid_geometry'].append(poly)
+                self.new_tools[current_tool]['solid_geometry'].append(poly)
 
         ordered_edited_slot_points = sorted(zip(edited_slot_points.keys(), edited_slot_points.values()))
         for tool_dia in ordered_edited_slot_points:
 
             tool_exist_flag = False
             for tool in self.new_tools:
-                if tool_dia[0] == self.new_tools[tool]["C"]:
+                if tool_dia[0] == self.new_tools[tool]["tooldia"]:
                     current_tool = tool
                     tool_exist_flag = True
                     break
 
             if tool_exist_flag is False:
                 current_tool += 1
+
                 # create the self.tools for the new Excellon object (the one with edited content)
-                name = str(current_tool)
-                spec = {"C": float(tool_dia[0])}
-                self.new_tools[name] = spec
+                if current_tool not in self.new_tools:
+                    self.new_tools[current_tool] = {}
+                self.new_tools[current_tool]['tooldia'] = float(tool_dia[0])
 
                 # add in self.tools the 'solid_geometry' key, the value (a list) is populated below
-                self.new_tools[name]['solid_geometry'] = []
+                self.new_tools[current_tool]['solid_geometry'] = []
 
             # create the self.slots for the new Excellon object (the one with edited content)
             for coord_dict in tool_dia[1]:
-                self.new_slots.append(
-                    {
-                        'start': Point(coord_dict['start']),
-                        'stop': Point(coord_dict['stop']),
-                        'tool': str(current_tool)
-                    }
+                slot = (
+                    Point(coord_dict['start']),
+                    Point(coord_dict['stop'])
                 )
+                try:
+                    self.new_tools[current_tool]['slots'].append(slot)
+                except KeyError:
+                    self.new_tools[current_tool]['slots'] = [slot]
+
                 # repopulate the 'solid_geometry' for each tool
                 poly = LineString([coord_dict['start'], coord_dict['stop']]).buffer(
                     float(tool_dia[0]) / 2.0, int(int(exc_obj.geo_steps_per_circle) / 4)
                 )
-                self.new_tools[str(current_tool)]['solid_geometry'].append(poly)
+                self.new_tools[current_tool]['solid_geometry'].append(poly)
 
         if self.is_modified is True:
             if "_edit" in self.edited_obj_name:

+ 1 - 1
appGUI/GUIElements.py

@@ -682,7 +682,7 @@ class NumericalEvalEntry(EvalEntry):
         return evaled
 
 
-class NumericalEvalTupleEntry(FCEntry):
+class NumericalEvalTupleEntry(EvalEntry):
     """
     Will return a text value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
     """

+ 140 - 204
appObjects/FlatCAMExcellon.py

@@ -138,7 +138,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.ser_attrs += ['options', 'kind']
 
     @staticmethod
-    def merge(exc_list, exc_final, decimals=None):
+    def merge(exc_list, exc_final, decimals=None, fuse_tools=True):
         """
         Merge Excellon objects found in exc_list parameter into exc_final object.
         Options are always copied from source .
@@ -153,31 +153,31 @@ class ExcellonObject(FlatCAMObj, Excellon):
         :type exc_list:     list
         :param exc_final:   Destination ExcellonObject object.
         :type exc_final:    class
+        :param decimals:    The number of decimals to be used for diameters
+        :type decimals:     int
+        :param fuse_tools:  If True will try to fuse tools of the same diameter for the Excellon objects
+        :type fuse_tools:   bool
         :return:            None
         """
 
+        if exc_final.tools is None:
+            exc_final.tools = {}
+
         if decimals is None:
             decimals = 4
         decimals_exc = decimals
 
-        # flag to signal that we need to reorder the tools dictionary and drills and slots lists
-        flag_order = False
-
         try:
             flattened_list = list(itertools.chain(*exc_list))
         except TypeError:
             flattened_list = exc_list
 
-        # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
-        # values will be list of Shapely Points; for drills
-        custom_dict_drills = defaultdict(list)
-
-        # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict
-        # values will be list of Shapely Points; for slots
-        custom_dict_slots = defaultdict(list)
-
+        new_tools = {}
+        total_geo = []
+        toolid = 0
         for exc in flattened_list:
             # copy options of the current excellon obj to the final excellon obj
+            # only the last object options will survive
             for option in exc.options:
                 if option != 'name':
                     try:
@@ -185,129 +185,47 @@ class ExcellonObject(FlatCAMObj, Excellon):
                     except Exception:
                         exc.app.log.warning("Failed to copy option.", option)
 
-            for drill in exc.drills:
-                exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[drill['tool']]['C']))
-                custom_dict_drills[exc_tool_dia].append(drill['point'])
-
-            for slot in exc.slots:
-                exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[slot['tool']]['C']))
-                custom_dict_slots[exc_tool_dia].append([slot['start'], slot['stop']])
+            for tool in exc.tools:
+                toolid += 1
+                new_tools[toolid] = exc.tools[tool]
 
+            exc_final.tools = deepcopy(new_tools)
             # add the zeros and units to the exc_final object
             exc_final.zeros = exc.zeros
             exc_final.units = exc.units
-
-        # ##########################################
-        # Here we add data to the exc_final object #
-        # ##########################################
-
-        # variable to make tool_name for the tools
-        current_tool = 0
-        # The tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of
-        # drills
-        for tool_dia in custom_dict_drills:
-            # we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter)
-            current_tool += 1
-
-            tool_name = str(current_tool)
-            spec = {"C": float(tool_dia)}
-            exc_final.tools[tool_name] = spec
-
-            # rebuild the drills list of dict's that belong to the exc_final object
-            for point in custom_dict_drills[tool_dia]:
-                exc_final.drills.append(
-                    {
-                        "point": point,
-                        "tool": str(current_tool)
-                    }
-                )
-
-        # The tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop])
-        # of two Shapely Points in case of slots
-        for tool_dia in custom_dict_slots:
-            # we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter)
-            # but only if there are no drills
-            if not exc_final.tools:
-                current_tool += 1
-                tool_name = str(current_tool)
-                spec = {"C": float(tool_dia)}
-                exc_final.tools[tool_name] = spec
-            else:
-                dia_list = []
-                for v in exc_final.tools.values():
-                    dia_list.append(float(v["C"]))
-
-                if tool_dia not in dia_list:
-                    flag_order = True
-
-                    current_tool = len(dia_list) + 1
-                    tool_name = str(current_tool)
-                    spec = {"C": float(tool_dia)}
-                    exc_final.tools[tool_name] = spec
-
-                else:
-                    for k, v in exc_final.tools.items():
-                        if v["C"] == tool_dia:
-                            current_tool = int(k)
+            total_geo += exc.solid_geometry
+
+        exc_final.solid_geometry = total_geo
+
+        fused_tools_dict = {}
+        if exc_final.tools and fuse_tools:
+            toolid = 0
+            for tool, tool_dict in exc_final.tools.items():
+                current_tooldia = float('%.*f' % (decimals_exc, tool_dict['tooldia']))
+                toolid += 1
+
+                # calculate all diameters in fused_tools_dict
+                all_dia = []
+                if fused_tools_dict:
+                    for f_tool in fused_tools_dict:
+                        all_dia.append(float('%.*f' % (decimals_exc, fused_tools_dict[f_tool]['tooldia'])))
+
+                if current_tooldia in all_dia:
+                    # find tool for current_tooldia in fuse_tools
+                    t = None
+                    for f_tool in fused_tools_dict:
+                        if fused_tools_dict[f_tool]['tooldia'] == current_tooldia:
+                            t = f_tool
                             break
+                    if t:
+                        fused_tools_dict[t]['drills'] += tool_dict['drills']
+                        fused_tools_dict[t]['slots'] += tool_dict['slots']
+                        fused_tools_dict[t]['solid_geometry'] += tool_dict['solid_geometry']
+                else:
+                    fused_tools_dict[toolid] = tool_dict
+                    fused_tools_dict[toolid]['tooldia'] = current_tooldia
 
-            # rebuild the slots list of dict's that belong to the exc_final object
-            for point in custom_dict_slots[tool_dia]:
-                exc_final.slots.append(
-                    {
-                        "start": point[0],
-                        "stop": point[1],
-                        "tool": str(current_tool)
-                    }
-                )
-
-        # flag_order == True means that there was an slot diameter not in the tools and we also have drills
-        # and the new tool was added to self.tools therefore we need to reorder the tools and drills and slots
-        current_tool = 0
-        if flag_order is True:
-            dia_list = []
-            temp_drills = []
-            temp_slots = []
-            temp_tools = {}
-            for v in exc_final.tools.values():
-                dia_list.append(float(v["C"]))
-            dia_list.sort()
-            for ordered_dia in dia_list:
-                current_tool += 1
-                tool_name_temp = str(current_tool)
-                spec_temp = {"C": float(ordered_dia)}
-                temp_tools[tool_name_temp] = spec_temp
-
-                for drill in exc_final.drills:
-                    exc_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[drill['tool']]['C']))
-                    if exc_tool_dia == ordered_dia:
-                        temp_drills.append(
-                            {
-                                "point": drill["point"],
-                                "tool": str(current_tool)
-                            }
-                        )
-
-                for slot in exc_final.slots:
-                    slot_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[slot['tool']]['C']))
-                    if slot_tool_dia == ordered_dia:
-                        temp_slots.append(
-                            {
-                                "start": slot["start"],
-                                "stop": slot["stop"],
-                                "tool": str(current_tool)
-                            }
-                        )
-
-            # delete the exc_final tools, drills and slots
-            exc_final.tools = {}
-            exc_final.drills[:] = []
-            exc_final.slots[:] = []
-
-            # update the exc_final tools, drills and slots with the ordered values
-            exc_final.tools = temp_tools
-            exc_final.drills[:] = temp_drills
-            exc_final.slots[:] = temp_slots
+            exc_final.tools = fused_tools_dict
 
         # create the geometry for the exc_final object
         exc_final.create_geometry()
@@ -351,7 +269,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
         sort = []
         for k, v in list(self.tools.items()):
-            sort.append((k, v.get('C')))
+            sort.append((k, v['tooldia']))
         sorted_tools = sorted(sort, key=lambda t1: t1[1])
         tools = [i[0] for i in sorted_tools]
 
@@ -370,23 +288,23 @@ class ExcellonObject(FlatCAMObj, Excellon):
             slot_cnt = 0  # variable to store the nr of slots per tool
 
             # Find no of drills for the current tool
-            for drill in self.drills:
-                if drill['tool'] == tool_no:
-                    drill_cnt += 1
-
+            try:
+                drill_cnt = len(self.tools[tool_no]['drills'])
+            except KeyError:
+                drill_cnt = 0
             self.tot_drill_cnt += drill_cnt
 
             # Find no of slots for the current tool
-            for slot in self.slots:
-                if slot['tool'] == tool_no:
-                    slot_cnt += 1
-
+            try:
+                slot_cnt = len(self.tools[tool_no]['slots'])
+            except KeyError:
+                slot_cnt = 0
             self.tot_slot_cnt += slot_cnt
 
             exc_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_no))
             exc_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
-            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['C']))
+            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['tooldia']))
             dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
 
             drill_count_item = QtWidgets.QTableWidgetItem('%d' % drill_cnt)
@@ -508,14 +426,26 @@ class ExcellonObject(FlatCAMObj, Excellon):
         self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight())
         self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight())
 
-        if not self.drills:
+        # find if we have drills:
+        has_drills = None
+        for tt in self.tools:
+            if 'drills' in self.tools[tt] and self.tools[tt]['drills']:
+                has_drills = True
+                break
+        if has_drills is None:
             self.ui.tooldia_entry.hide()
             self.ui.generate_milling_button.hide()
         else:
             self.ui.tooldia_entry.show()
             self.ui.generate_milling_button.show()
 
-        if not self.slots:
+        # find if we have slots
+        has_slots = None
+        for tt in self.tools:
+            if 'slots' in self.tools[tt] and self.tools[tt]['slots']:
+                has_slots = True
+                break
+        if has_slots is None:
             self.ui.slot_tooldia_entry.hide()
             self.ui.generate_milling_slots_button.hide()
         else:
@@ -962,7 +892,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
         :rtype:     list
         """
 
-        return [str(x.text()) for x in self.ui.tools_table.selectedItems()]
+        return [x.text() for x in self.ui.tools_table.selectedItems()]
 
     def get_selected_tools_table_items(self):
         """
@@ -1017,23 +947,37 @@ class ExcellonObject(FlatCAMObj, Excellon):
         excellon_code = ''
 
         # store here if the file has slots, return 1 if any slots, 0 if only drills
-        has_slots = 0
+        slots_in_file = 0
+
+        # find if we have drills:
+        has_drills = None
+        for tt in self.tools:
+            if 'drills' in self.tools[tt] and self.tools[tt]['drills']:
+                has_drills = True
+                break
+        # find if we have slots:
+        has_slots = None
+        for tt in self.tools:
+            if 'slots' in self.tools[tt] and self.tools[tt]['slots']:
+                has_slots = True
+                slots_in_file = 1
+                break
 
         # drills processing
         try:
-            if self.drills:
+            if has_drills:
                 length = whole + fract
                 for tool in self.tools:
                     excellon_code += 'T0%s\n' % str(tool) if int(tool) < 10 else 'T%s\n' % str(tool)
 
-                    for drill in self.drills:
-                        if form == 'dec' and tool == drill['tool']:
-                            drill_x = drill['point'].x * factor
-                            drill_y = drill['point'].y * factor
+                    for drill in self.tools[tool]['drills']:
+                        if form == 'dec':
+                            drill_x = drill.x * factor
+                            drill_y = drill.y * factor
                             excellon_code += "X{:.{dec}f}Y{:.{dec}f}\n".format(drill_x, drill_y, dec=fract)
-                        elif e_zeros == 'LZ' and tool == drill['tool']:
-                            drill_x = drill['point'].x * factor
-                            drill_y = drill['point'].y * factor
+                        elif e_zeros == 'LZ':
+                            drill_x = drill.x * factor
+                            drill_y = drill.y * factor
 
                             exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract)
                             exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract)
@@ -1053,9 +997,9 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
                             excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted,
                                                                          yform=exc_y_formatted)
-                        elif tool == drill['tool']:
-                            drill_x = drill['point'].x * factor
-                            drill_y = drill['point'].y * factor
+                        else:
+                            drill_x = drill.x * factor
+                            drill_y = drill.y * factor
 
                             exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract).replace('.', '')
                             exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract).replace('.', '')
@@ -1071,8 +1015,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
         # slots processing
         try:
-            if self.slots:
-                has_slots = 1
+            if has_slots:
                 for tool in self.tools:
                     excellon_code += 'G05\n'
 
@@ -1081,12 +1024,12 @@ class ExcellonObject(FlatCAMObj, Excellon):
                     else:
                         excellon_code += 'T' + str(tool) + '\n'
 
-                    for slot in self.slots:
-                        if form == 'dec' and tool == slot['tool']:
-                            start_slot_x = slot['start'].x * factor
-                            start_slot_y = slot['start'].y * factor
-                            stop_slot_x = slot['stop'].x * factor
-                            stop_slot_y = slot['stop'].y * factor
+                    for slot in self.tools[tool]['slots']:
+                        if form == 'dec':
+                            start_slot_x = slot.x * factor
+                            start_slot_y = slot.y * factor
+                            stop_slot_x = slot.x * factor
+                            stop_slot_y = slot.y * factor
                             if slot_type == 'routing':
                                 excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x,
                                                                                            start_slot_y,
@@ -1099,11 +1042,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
                                     start_slot_x, start_slot_y, stop_slot_x, stop_slot_y, dec=fract
                                 )
 
-                        elif e_zeros == 'LZ' and tool == slot['tool']:
-                            start_slot_x = slot['start'].x * factor
-                            start_slot_y = slot['start'].y * factor
-                            stop_slot_x = slot['stop'].x * factor
-                            stop_slot_y = slot['stop'].y * factor
+                        elif e_zeros == 'LZ':
+                            start_slot_x = slot.x * factor
+                            start_slot_y = slot.y * factor
+                            stop_slot_x = slot.x * factor
+                            stop_slot_y = slot.y * factor
 
                             start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '')
                             start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '')
@@ -1139,11 +1082,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
                                     xstart=start_slot_x_formatted, ystart=start_slot_y_formatted,
                                     xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted
                                 )
-                        elif tool == slot['tool']:
-                            start_slot_x = slot['start'].x * factor
-                            start_slot_y = slot['start'].y * factor
-                            stop_slot_x = slot['stop'].x * factor
-                            stop_slot_y = slot['stop'].y * factor
+                        else:
+                            start_slot_x = slot.x * factor
+                            start_slot_y = slot.y * factor
+                            stop_slot_x = slot.x * factor
+                            stop_slot_y = slot.y * factor
                             length = whole + fract
 
                             start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '')
@@ -1170,11 +1113,11 @@ class ExcellonObject(FlatCAMObj, Excellon):
         except Exception as e:
             log.debug(str(e))
 
-        if not self.drills and not self.slots:
+        if not has_drills and not has_slots:
             log.debug("FlatCAMObj.ExcellonObject.export_excellon() --> Excellon Object is empty: no drills, no slots.")
             return 'fail'
 
-        return has_slots, excellon_code
+        return slots_in_file, excellon_code
 
     def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
         """
@@ -1215,7 +1158,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
         sort = []
         for k, v in self.tools.items():
-            sort.append((k, v.get('C')))
+            sort.append((k, v['tooldia']))
         sorted_tools = sorted(sort, key=lambda t1: t1[1])
 
         if tools == "all":
@@ -1223,19 +1166,15 @@ class ExcellonObject(FlatCAMObj, Excellon):
             log.debug("Tools 'all' and sorted are: %s" % str(tools))
 
         if len(tools) == 0:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Please select one or more tools from the list and try again."))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Please select one or more tools from the list and try again."))
             return False, "Error: No tools."
 
         for tool in tools:
-            if tooldia > self.tools[tool]["C"]:
-                self.app.inform.emit(
-                    '[ERROR_NOTCL] %s %s: %s' % (
-                        _("Milling tool for DRILLS is larger than hole size. Cancelled."),
-                        _("Tool"),
-                        str(tool)
-                    )
-                )
+            if tooldia > self.tools[tool]["tooldia"]:
+                mseg = '[ERROR_NOTCL] %s %s: %s' % (_("Milling tool for DRILLS is larger than hole size. Cancelled."),
+                                                    _("Tool"),
+                                                    str(tool))
+                self.app.inform.emit(mseg)
                 return False, "Error: Milling tool is larger than hole."
 
         def geo_init(geo_obj, app_obj):
@@ -1266,15 +1205,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # for FlatCAM is 6 decimals,
             # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero"
-            for hole in self.drills:
-                if hole['tool'] in tools:
-                    buffer_value = self.tools[hole['tool']]["C"] / 2 - tooldia / 2
+            for tool in tools:
+                for drill in self.tools[tool]['drills']:
+                    buffer_value = self.tools[tool]['tooldia'] / 2 - tooldia / 2
                     if buffer_value == 0:
-                        geo_obj.solid_geometry.append(
-                            Point(hole['point']).buffer(0.0000001).exterior)
+                        geo_obj.solid_geometry.append(drill.buffer(0.0000001).exterior)
                     else:
-                        geo_obj.solid_geometry.append(
-                            Point(hole['point']).buffer(buffer_value).exterior)
+                        geo_obj.solid_geometry.append(drill.buffer(buffer_value).exterior)
 
         if use_thread:
             def geo_thread(a_obj):
@@ -1329,7 +1266,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
 
         sort = []
         for k, v in self.tools.items():
-            sort.append((k, v.get('C')))
+            sort.append((k, v['tooldia']))
         sorted_tools = sorted(sort, key=lambda t1: t1[1])
 
         if tools == "all":
@@ -1337,14 +1274,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
             log.debug("Tools 'all' and sorted are: %s" % str(tools))
 
         if len(tools) == 0:
-            self.app.inform.emit('[ERROR_NOTCL] %s' %
-                                 _("Please select one or more tools from the list and try again."))
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Please select one or more tools from the list and try again."))
             return False, "Error: No tools."
 
         for tool in tools:
             # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
             adj_toolstable_tooldia = float('%.*f' % (self.decimals, float(tooldia)))
-            adj_file_tooldia = float('%.*f' % (self.decimals, float(self.tools[tool]["C"])))
+            adj_file_tooldia = float('%.*f' % (self.decimals, float(self.tools[tool]["tooldia"])))
             if adj_toolstable_tooldia > adj_file_tooldia + 0.0001:
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("Milling tool for SLOTS is larger than hole size. Cancelled."))
@@ -1369,24 +1305,24 @@ class ExcellonObject(FlatCAMObj, Excellon):
             # in case that the tool used has the same diameter with the hole, and since the maximum resolution
             # for FlatCAM is 6 decimals,
             # 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:
-                if slot['tool'] in tools:
+            for tool in tools:
+                for slot in self.tools[tool]['slots']:
                     toolstable_tool = float('%.*f' % (self.decimals, float(tooldia)))
-                    file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["C"])))
+                    file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["tooldia"])))
 
                     # 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:
-                        start = slot['start']
-                        stop = slot['stop']
+                        start = slot[0]
+                        stop = slot[1]
 
                         lines_string = LineString([start, stop])
                         poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior
                         geo_obj.solid_geometry.append(poly)
                     else:
-                        start = slot['start']
-                        stop = slot['stop']
+                        start = slot[0]
+                        stop = slot[1]
 
                         lines_string = LineString([start, stop])
                         poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior
@@ -1522,7 +1458,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
             # tool number) it means that there are 3 rows (1 tool and 2 totals).
             # in this case regardless of the selection status of that tool, use it.
             if self.ui.tools_table.rowCount() == 3:
-                tools.append(self.ui.tools_table.item(0, 0).text())
+                tools.append(int(self.ui.tools_table.item(0, 0).text()))
             else:
                 self.app.inform.emit('[ERROR_NOTCL] %s' %
                                      _("Please select one or more tools from the list and try again."))

+ 271 - 227
appParsers/ParseExcellon.py

@@ -40,30 +40,13 @@ class Excellon(Geometry):
     ================  ====================================
     Key               Value
     ================  ====================================
-    C                 Diameter of the tool
-    solid_geometry    Geometry list for each tool
+    tooldia           Diameter of the tool
+    drills            List that store the Shapely Points for drill points
+    slots             List that store the Shapely Points for slots. Each is a tuple: (start_point, stop_point
     data              dictionary which holds the options for each tool
-    Others            Not supported (Ignored).
-    ================  ====================================
-
-    * ``drills`` (list): Each is a dictionary:
-
-    ================  ====================================
-    Key               Value
-    ================  ====================================
-    point             (Shapely.Point) Where to drill
-    tool              (str) A key in ``tools``
+    solid_geometry    Geometry list for each tool
     ================  ====================================
 
-    * ``slots`` (list): Each is a dictionary
-
-    ================  ====================================
-    Key               Value
-    ================  ====================================
-    start             (Shapely.Point) Start point of the slot
-    stop              (Shapely.Point) Stop point of the slot
-    tool              (str) A key in ``tools``
-    ================  ====================================
     """
 
     defaults = {
@@ -96,10 +79,6 @@ class Excellon(Geometry):
 
         # dictionary to store tools, see above for description
         self.tools = {}
-        # list to store the drills, see above for description
-        self.drills = []
-        # self.slots (list) to store the slots; each is a dictionary
-        self.slots = []
 
         self.source_file = ''
 
@@ -110,9 +89,6 @@ class Excellon(Geometry):
         self.match_routing_start = None
         self.match_routing_stop = None
 
-        self.num_tools = []  # List for keeping the tools sorted
-        self.index_per_tool = {}  # Dictionary to store the indexed points for each tool
-
         # ## IN|MM -> Units are inherited from Geometry
         self.units = self.app.defaults['units']
         self.units_found = self.app.defaults['units']
@@ -142,9 +118,8 @@ class Excellon(Geometry):
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # from Geometry.
-        self.ser_attrs += ['tools', 'drills', 'zeros', 'excellon_format_upper_mm', 'excellon_format_lower_mm',
-                           'excellon_format_upper_in', 'excellon_format_lower_in', 'excellon_units', 'slots',
-                           'source_file']
+        self.ser_attrs += ['tools', 'zeros', 'excellon_format_upper_mm', 'excellon_format_lower_mm',
+                           'excellon_format_upper_in', 'excellon_format_lower_in', 'excellon_units', 'source_file']
 
         # ### Patterns ####
         # Regex basics:
@@ -346,14 +321,22 @@ class Excellon(Geometry):
 
                         if match.group(2):
                             name_tool += 1
+
+                            # ----------  add a TOOL  ------------ #
+                            if name_tool not in self.tools:
+                                self.tools[name_tool] = {}
                             if line_units == 'MILS':
-                                spec = {"C": (float(match.group(2)) / 1000)}
-                                self.tools[str(name_tool)] = spec
-                                log.debug("Tool definition: %s %s" % (name_tool, spec))
+                                spec = {
+                                    'tooldia':  (float(match.group(2)) / 1000)
+                                }
+                                self.tools[name_tool]['tooldia'] = (float(match.group(2)) / 1000)
+                                log.debug("Tool definition: %d %s" % (name_tool, spec))
                             else:
-                                spec = {"C": float(match.group(2))}
-                                self.tools[str(name_tool)] = spec
-                                log.debug("Tool definition: %s %s" % (name_tool, spec))
+                                spec = {
+                                    'tooldia': float(match.group(2))
+                                }
+                                self.tools[name_tool]['tooldia'] = float(match.group(2))
+                                log.debug("Tool definition: %d %s" % (name_tool, spec))
                             spec['solid_geometry'] = []
                             continue
                     # search for Altium Excellon Format / Sprint Layout who is included as a comment
@@ -400,7 +383,7 @@ class Excellon(Geometry):
                         lower_tools = set()
                         if not self.excellon_units_found and self.tools:
                             for tool in self.tools:
-                                tool_dia = float(self.tools[tool]['C'])
+                                tool_dia = float(self.tools[tool]['tooldia'])
                                 lower_tools.add(tool_dia) if tool_dia <= 0.1 else greater_tools.add(tool_dia)
 
                             assumed_units = "IN" if len(lower_tools) > len(greater_tools) else "MM"
@@ -434,12 +417,12 @@ class Excellon(Geometry):
                     # ## Tool change ###
                     match = self.toolsel_re.search(eline)
                     if match:
-                        current_tool = str(int(match.group(1)))
+                        current_tool = int(match.group(1))
                         log.debug("Tool change: %s" % current_tool)
                         if bool(headerless):
                             match = self.toolset_hl_re.search(eline)
                             if match:
-                                name = str(int(match.group(1)))
+                                name = int(match.group(1))
                                 try:
                                     diam = float(match.group(2))
                                 except Exception:
@@ -467,8 +450,13 @@ class Excellon(Geometry):
                                     else:
                                         diam = (self.toolless_diam + (int(current_tool) - 1) / 100) / 25.4
 
-                                spec = {"C": diam, 'solid_geometry': []}
-                                self.tools[name] = spec
+                                # ----------  add a TOOL  ------------ #
+                                spec = {"tooldia": diam, 'solid_geometry': []}
+                                if name not in self.tools:
+                                    self.tools[name] = {}
+                                self.tools[name]['tooldia'] = diam
+                                self.tools[name]['solid_geometry'] = []
+
                                 log.debug("Tool definition out of header: %s %s" % (name, spec))
 
                         continue
@@ -479,8 +467,8 @@ class Excellon(Geometry):
                         match1 = self.stop_re.search(eline)
                         if match or match1:
                             name_tool += 1
-                            current_tool = str(name_tool)
-                            log.debug("Tool change for Allegro type of Excellon: %s" % current_tool)
+                            current_tool = name_tool
+                            log.debug("Tool change for Allegro type of Excellon: %d" % current_tool)
                             continue
 
                     # ## Slots parsing for drilled slots (contain G85)
@@ -546,7 +534,7 @@ class Excellon(Geometry):
                             # store current tool diameter as slot diameter
                             slot_dia = 0.05
                             try:
-                                slot_dia = float(self.tools[current_tool]['C'])
+                                slot_dia = float(self.tools[current_tool]['tooldia'])
                             except Exception:
                                 pass
                             log.debug(
@@ -556,13 +544,17 @@ class Excellon(Geometry):
                                 )
                             )
 
-                            self.slots.append(
-                                {
-                                    'start': Point(slot_start_x, slot_start_y),
-                                    'stop': Point(slot_stop_x, slot_stop_y),
-                                    'tool': current_tool
-                                }
+                            # ----------  add a slot  ------------ #
+                            slot = (
+                                Point(slot_start_x, slot_start_y),
+                                Point(slot_stop_x, slot_stop_y)
                             )
+                            if current_tool not in self.tools:
+                                self.tools[current_tool] = {}
+                            if 'slots' in self.tools[current_tool]:
+                                self.tools[current_tool]['slots'].apend(slot)
+                            else:
+                                self.tools[current_tool]['slots'] = [slot]
                             continue
 
                         # Slot coordinates with period: Use literally. ###
@@ -616,7 +608,7 @@ class Excellon(Geometry):
                             # store current tool diameter as slot diameter
                             slot_dia = 0.05
                             try:
-                                slot_dia = float(self.tools[current_tool]['C'])
+                                slot_dia = float(self.tools[current_tool]['tooldia'])
                             except Exception:
                                 pass
                             log.debug(
@@ -626,13 +618,17 @@ class Excellon(Geometry):
                                 )
                             )
 
-                            self.slots.append(
-                                {
-                                    'start': Point(slot_start_x, slot_start_y),
-                                    'stop': Point(slot_stop_x, slot_stop_y),
-                                    'tool': current_tool
-                                }
+                            # ----------  add a Slot  ------------ #
+                            slot = (
+                                Point(slot_start_x, slot_start_y),
+                                Point(slot_stop_x, slot_stop_y)
                             )
+                            if current_tool not in self.tools:
+                                self.tools[current_tool] = {}
+                            if 'slots' in self.tools[current_tool]:
+                                self.tools[current_tool]['slots'].apend(slot)
+                            else:
+                                self.tools[current_tool]['slots'] = [slot]
                         continue
 
                     # ## Coordinates without period # ##
@@ -660,7 +656,14 @@ class Excellon(Geometry):
                                     coordx += repeating_x
                                 if repeating_y:
                                     coordy += repeating_y
-                                self.drills.append({'point': Point((coordx, coordy)), 'tool': current_tool})
+
+                                # ----------  add a Drill  ------------ #
+                                if current_tool not in self.tools:
+                                    self.tools[current_tool] = {}
+                                if 'drills' in self.tools[current_tool]:
+                                    self.tools[current_tool]['drills'].append(Point((coordx, coordy)))
+                                else:
+                                    self.tools[current_tool]['drills'] = [Point((coordx, coordy))]
 
                                 repeat -= 1
                             current_x = coordx
@@ -710,19 +713,31 @@ class Excellon(Geometry):
                                     self.routing_flag = 1
                                     slot_stop_x = x
                                     slot_stop_y = y
-                                    self.slots.append(
-                                        {
-                                            'start': Point(slot_start_x, slot_start_y),
-                                            'stop': Point(slot_stop_x, slot_stop_y),
-                                            'tool': current_tool
-                                        }
+
+                                    # ----------  add a Slot  ------------ #
+                                    slot = (
+                                        Point(slot_start_x, slot_start_y),
+                                        Point(slot_stop_x, slot_stop_y)
                                     )
+                                    if current_tool not in self.tools:
+                                        self.tools[current_tool] = {}
+                                    if 'slots' in self.tools[current_tool]:
+                                        self.tools[current_tool]['slots'].apend(slot)
+                                    else:
+                                        self.tools[current_tool]['slots'] = [slot]
                                     continue
 
                             if self.match_routing_start is None and self.match_routing_stop is None:
                                 # signal that there are drill operations
                                 self.defaults['excellon_drills'] = True
-                                self.drills.append({'point': Point((x, y)), 'tool': current_tool})
+
+                                # ----------  add a Drill  ------------ #
+                                if current_tool not in self.tools:
+                                    self.tools[current_tool] = {}
+                                if 'drills' in self.tools[current_tool]:
+                                    self.tools[current_tool]['drills'].append(Point((x, y)))
+                                else:
+                                    self.tools[current_tool]['drills'] = [Point((x, y))]
                                 # log.debug("{:15} {:8} {:8}".format(eline, x, y))
                                 continue
 
@@ -778,13 +793,18 @@ class Excellon(Geometry):
                                 self.routing_flag = 1
                                 slot_stop_x = x
                                 slot_stop_y = y
-                                self.slots.append(
-                                    {
-                                        'start': Point(slot_start_x, slot_start_y),
-                                        'stop': Point(slot_stop_x, slot_stop_y),
-                                        'tool': current_tool
-                                    }
+
+                                # ----------  add a Slot  ------------ #
+                                slot = (
+                                    Point(slot_start_x, slot_start_y),
+                                    Point(slot_stop_x, slot_stop_y)
                                 )
+                                if current_tool not in self.tools:
+                                    self.tools[current_tool] = {}
+                                if 'slots' in self.tools[current_tool]:
+                                    self.tools[current_tool]['slots'].apend(slot)
+                                else:
+                                    self.tools[current_tool]['slots'] = [slot]
                                 continue
 
                         if self.match_routing_start is None and self.match_routing_stop is None:
@@ -792,7 +812,14 @@ class Excellon(Geometry):
                             if repeat == 0:
                                 # signal that there are drill operations
                                 self.defaults['excellon_drills'] = True
-                                self.drills.append({'point': Point((x, y)), 'tool': current_tool})
+
+                                # ----------  add a Drill  ------------ #
+                                if current_tool not in self.tools:
+                                    self.tools[current_tool] = {}
+                                if 'drills' in self.tools[current_tool]:
+                                    self.tools[current_tool]['drills'].append(Point((x, y)))
+                                else:
+                                    self.tools[current_tool]['drills'] = [Point((x, y))]
                             else:
                                 coordx = x
                                 coordy = y
@@ -801,7 +828,15 @@ class Excellon(Geometry):
                                         coordx = (repeat * x) + repeating_x
                                     if repeating_y:
                                         coordy = (repeat * y) + repeating_y
-                                    self.drills.append({'point': Point((coordx, coordy)), 'tool': current_tool})
+
+                                    # ----------  add a Drill  ------------ #
+                                    if current_tool not in self.tools:
+                                        self.tools[current_tool] = {}
+                                    if 'drills' in self.tools[current_tool]:
+                                        self.tools[current_tool]['drills'].append(Point((coordx, coordy)))
+                                    else:
+                                        self.tools[current_tool]['drills'] = [Point((coordx, coordy))]
+
                                     repeat -= 1
                             repeating_x = repeating_y = 0
                             # log.debug("{:15} {:8} {:8}".format(eline, x, y))
@@ -813,9 +848,14 @@ class Excellon(Geometry):
                     # ## Tool definitions # ##
                     match = self.toolset_re.search(eline)
                     if match:
-                        name = str(int(match.group(1)))
+                        # ----------  add a TOOL  ------------ #
+                        name = int(match.group(1))
                         spec = {"C": float(match.group(2)), 'solid_geometry': []}
-                        self.tools[name] = spec
+                        if name not in self.tools:
+                            self.tools[name] = {}
+                        self.tools[name]['tooldia'] = float(match.group(2))
+                        self.tools[name]['solid_geometry'] = []
+
                         log.debug("Tool definition: %s %s" % (name, spec))
                         continue
 
@@ -977,8 +1017,9 @@ class Excellon(Geometry):
     def create_geometry(self):
         """
         Creates circles of the tool diameter at every point
-        specified in ``self.drills``. Also creates geometries (polygons)
-        for the slots as specified in ``self.slots``
+        specified in self.tools[tool]['drills'].
+        Also creates geometries (polygons)
+        for the slots as specified in self.tools[tool]['slots']
         All the resulting geometry is stored into self.solid_geometry list.
         The list self.solid_geometry has 2 elements: first is a dict with the drills geometry,
         and second element is another similar dict that contain the slots geometry.
@@ -1001,36 +1042,35 @@ class Excellon(Geometry):
                 self.tools[tool]['solid_geometry'] = []
                 self.tools[tool]['data'] = {}
 
-            for drill in self.drills:
-                # poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0)
-                if drill['tool'] == '':
-                    self.app.inform.emit('[WARNING] %s' %
-                                         _("Excellon.create_geometry() -> a drill location was skipped "
-                                           "due of not having a tool associated.\n"
-                                           "Check the resulting GCode."))
-                    log.debug("appParsers.ParseExcellon.Excellon.create_geometry() -> a drill location was skipped "
-                              "due of not having a tool associated")
-                    continue
-                tooldia = self.tools[drill['tool']]['C']
-                poly = drill['point'].buffer(tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
-                self.solid_geometry.append(poly)
-
-                tool_in_drills = drill['tool']
-                self.tools[tool_in_drills]['solid_geometry'].append(poly)
-                self.tools[tool_in_drills]['data'] = deepcopy(self.default_data)
-
-            for slot in self.slots:
-                slot_tooldia = self.tools[slot['tool']]['C']
-                start = slot['start']
-                stop = slot['stop']
-
-                lines_string = LineString([start, stop])
-                poly = lines_string.buffer(slot_tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
-                self.solid_geometry.append(poly)
-
-                tool_in_slots = slot['tool']
-                self.tools[tool_in_slots]['solid_geometry'].append(poly)
-                self.tools[tool_in_slots]['data'] = deepcopy(self.default_data)
+            for tool in self.tools:
+                tooldia = self.tools[tool]['tooldia']
+
+                if 'drills' in self.tools[tool]:
+                    for drill in self.tools[tool]['drills']:
+                        poly = drill.buffer(tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
+
+                        # add poly in the tools geometry
+                        self.tools[tool]['solid_geometry'].append(poly)
+                        self.tools[tool]['data'] = deepcopy(self.default_data)
+
+                        # add poly to the total solid geometry
+                        self.solid_geometry.append(poly)
+
+                if 'slots' in self.tools[tool]:
+                    for slot in self.tools[tool]['slots']:
+                        start = slot[0]
+                        stop = slot[1]
+
+                        lines_string = LineString([start, stop])
+                        poly = lines_string.buffer(tooldia / 2.0, int(int(self.geo_steps_per_circle) / 4))
+
+                        # add poly in the tools geometry
+                        self.tools[tool]['solid_geometry'].append(poly)
+                        self.tools[tool]['data'] = deepcopy(self.default_data)
+
+                        # add poly to the total solid geometry
+                        self.solid_geometry.append(poly)
+
         except Exception as e:
             log.debug("appParsers.ParseExcellon.Excellon.create_geometry() -> "
                       "Excellon geometry creation failed due of ERROR: %s" % str(e))
@@ -1126,7 +1166,7 @@ class Excellon(Geometry):
 
         # Tools
         for tname in self.tools:
-            self.tools[tname]["C"] *= factor
+            self.tools[tname]["tooldia"] *= factor
 
         self.create_geometry()
         return factor
@@ -1173,31 +1213,39 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            self.geo_len = len(self.drills)
+            self.geo_len = len(self.tools)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
         self.el_count = 0
 
-        # Drills
-        for drill in self.drills:
-            drill['point'] = affinity.scale(drill['point'], xfactor, yfactor, origin=(px, py))
+        for tool in self.tools:
+            # Scale Drills
+            if 'drills' in self.tools[tool]:
+                new_drills = []
+                for drill in self.tools[tool]['drills']:
+                    new_drills.append(affinity.scale(drill, xfactor, yfactor, origin=(px, py)))
+                self.tools[tool]['drills'] = new_drills
+
+            # Scale Slots
+            if 'slots' in self.tools[tool]:
+                new_slots = []
+                for slot in self.tools[tool]['slots']:
+                    new_start = affinity.scale(slot[0], xfactor, yfactor, origin=(px, py))
+                    new_stop = affinity.scale(slot[1], xfactor, yfactor, origin=(px, py))
+                    new_slot = (new_start, new_stop)
+                    new_slots.append(new_slot)
+
+            # Scale solid_geometry
+            self.tools[tool]['solid_geometry'] = scale_geom(self.tools[tool]['solid_geometry'])
 
+            # update status display
             self.el_count += 1
             disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
             if self.old_disp_number < disp_number <= 100:
                 self.app.proc_container.update_view_text(' %d%%' % disp_number)
                 self.old_disp_number = disp_number
 
-        # scale solid_geometry
-        for tool in self.tools:
-            self.tools[tool]['solid_geometry'] = scale_geom(self.tools[tool]['solid_geometry'])
-
-        # Slots
-        for slot in self.slots:
-            slot['stop'] = affinity.scale(slot['stop'], xfactor, yfactor, origin=(px, py))
-            slot['start'] = affinity.scale(slot['start'], xfactor, yfactor, origin=(px, py))
-
         self.create_geometry()
         self.app.proc_container.new_text = ''
 
@@ -1231,31 +1279,39 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            self.geo_len = len(self.drills)
+            self.geo_len = len(self.tools)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
         self.el_count = 0
 
-        # Drills
-        for drill in self.drills:
-            drill['point'] = affinity.translate(drill['point'], xoff=dx, yoff=dy)
+        for tool in self.tools:
+            # Offset Drills
+            if 'drills' in self.tools[tool]:
+                new_drills = []
+                for drill in self.tools[tool]['drills']:
+                    new_drills.append(affinity.translate(drill, xoff=dx, yoff=dy))
+                self.tools[tool]['drills'] = new_drills
+
+            # Offset Slots
+            if 'slots' in self.tools[tool]:
+                new_slots = []
+                for slot in self.tools[tool]['slots']:
+                    new_start = affinity.translate(slot[0], xoff=dx, yoff=dy)
+                    new_stop = affinity.translate(slot[1], xoff=dx, yoff=dy)
+                    new_slot = (new_start, new_stop)
+                    new_slots.append(new_slot)
+
+            # Offset solid_geometry
+            self.tools[tool]['solid_geometry'] = offset_geom(self.tools[tool]['solid_geometry'])
 
+            # update status display
             self.el_count += 1
             disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
             if self.old_disp_number < disp_number <= 100:
                 self.app.proc_container.update_view_text(' %d%%' % disp_number)
                 self.old_disp_number = disp_number
 
-        # offset solid_geometry
-        for tool in self.tools:
-            self.tools[tool]['solid_geometry'] = offset_geom(self.tools[tool]['solid_geometry'])
-
-        # Slots
-        for slot in self.slots:
-            slot['stop'] = affinity.translate(slot['stop'], xoff=dx, yoff=dy)
-            slot['start'] = affinity.translate(slot['start'], xoff=dx, yoff=dy)
-
         # Recreate geometry
         self.create_geometry()
         self.app.proc_container.new_text = ''
@@ -1291,31 +1347,39 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            self.geo_len = len(self.drills)
+            self.geo_len = len(self.tools)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
         self.el_count = 0
 
-        # Drills
-        for drill in self.drills:
-            drill['point'] = affinity.scale(drill['point'], xscale, yscale, origin=(px, py))
+        for tool in self.tools:
+            # Offset Drills
+            if 'drills' in self.tools[tool]:
+                new_drills = []
+                for drill in self.tools[tool]['drills']:
+                    new_drills.append(affinity.scale(drill, xscale, yscale, origin=(px, py)))
+                self.tools[tool]['drills'] = new_drills
+
+            # Offset Slots
+            if 'slots' in self.tools[tool]:
+                new_slots = []
+                for slot in self.tools[tool]['slots']:
+                    new_start = affinity.scale(slot[0], xscale, yscale, origin=(px, py))
+                    new_stop = affinity.scale(slot[1], xscale, yscale, origin=(px, py))
+                    new_slot = (new_start, new_stop)
+                    new_slots.append(new_slot)
+
+            # Offset solid_geometry
+            self.tools[tool]['solid_geometry'] = mirror_geom(self.tools[tool]['solid_geometry'])
 
+            # update status display
             self.el_count += 1
             disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
             if self.old_disp_number < disp_number <= 100:
                 self.app.proc_container.update_view_text(' %d%%' % disp_number)
                 self.old_disp_number = disp_number
 
-        # mirror solid_geometry
-        for tool in self.tools:
-            self.tools[tool]['solid_geometry'] = mirror_geom(self.tools[tool]['solid_geometry'])
-
-        # Slots
-        for slot in self.slots:
-            slot['stop'] = affinity.scale(slot['stop'], xscale, yscale, origin=(px, py))
-            slot['start'] = affinity.scale(slot['start'], xscale, yscale, origin=(px, py))
-
         # Recreate geometry
         self.create_geometry()
         self.app.proc_container.new_text = ''
@@ -1361,7 +1425,7 @@ class Excellon(Geometry):
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            self.geo_len = len(self.drills)
+            self.geo_len = len(self.tools)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
@@ -1369,47 +1433,35 @@ class Excellon(Geometry):
 
         if point is None:
             px, py = 0, 0
-
-            # Drills
-            for drill in self.drills:
-                drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
-                                               origin=(px, py))
-
-                self.el_count += 1
-                disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
-                if self.old_disp_number < disp_number <= 100:
-                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                    self.old_disp_number = disp_number
-
-            # skew solid_geometry
-            for tool in self.tools:
-                self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
-
-            # Slots
-            for slot in self.slots:
-                slot['stop'] = affinity.skew(slot['stop'], angle_x, angle_y, origin=(px, py))
-                slot['start'] = affinity.skew(slot['start'], angle_x, angle_y, origin=(px, py))
         else:
             px, py = point
-            # Drills
-            for drill in self.drills:
-                drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
-                                               origin=(px, py))
-
-                self.el_count += 1
-                disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
-                if self.old_disp_number < disp_number <= 100:
-                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                    self.old_disp_number = disp_number
-
-            # skew solid_geometry
-            for tool in self.tools:
-                self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
 
-            # Slots
-            for slot in self.slots:
-                slot['stop'] = affinity.skew(slot['stop'], angle_x, angle_y, origin=(px, py))
-                slot['start'] = affinity.skew(slot['start'], angle_x, angle_y, origin=(px, py))
+        for tool in self.tools:
+            # Offset Drills
+            if 'drills' in self.tools[tool]:
+                new_drills = []
+                for drill in self.tools[tool]['drills']:
+                    new_drills.append(affinity.skew(drill, angle_x, angle_y, origin=(px, py)))
+                self.tools[tool]['drills'] = new_drills
+
+            # Offset Slots
+            if 'slots' in self.tools[tool]:
+                new_slots = []
+                for slot in self.tools[tool]['slots']:
+                    new_start = affinity.skew(slot[0], angle_x, angle_y, origin=(px, py))
+                    new_stop = affinity.skew(slot[1], angle_x, angle_y, origin=(px, py))
+                    new_slot = (new_start, new_stop)
+                    new_slots.append(new_slot)
+
+            # Offset solid_geometry
+            self.tools[tool]['solid_geometry'] = skew_geom(self.tools[tool]['solid_geometry'])
+
+            # update status display
+            self.el_count += 1
+            disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
+            if self.old_disp_number < disp_number <= 100:
+                self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                self.old_disp_number = disp_number
 
         self.create_geometry()
         self.app.proc_container.new_text = ''
@@ -1441,58 +1493,50 @@ class Excellon(Geometry):
                         return obj
                 else:
                     try:
-                        return affinity.rotate(obj, angle, origin=(px, py))
+                        return affinity.rotate(obj, angle, origin=orig)
                     except AttributeError:
                         return obj
 
         # variables to display the percentage of work done
         self.geo_len = 0
         try:
-            self.geo_len = len(self.drills)
+            self.geo_len = len(self.tools)
         except TypeError:
             self.geo_len = 1
         self.old_disp_number = 0
         self.el_count = 0
 
         if point is None:
-            # Drills
-            for drill in self.drills:
-                drill['point'] = affinity.rotate(drill['point'], angle, origin='center')
-
-            # rotate solid_geometry
-            for tool in self.tools:
-                self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'], origin='center')
-
-                self.el_count += 1
-                disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
-                if self.old_disp_number < disp_number <= 100:
-                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                    self.old_disp_number = disp_number
-
-            # Slots
-            for slot in self.slots:
-                slot['stop'] = affinity.rotate(slot['stop'], angle, origin='center')
-                slot['start'] = affinity.rotate(slot['start'], angle, origin='center')
+            orig = 'center'
         else:
-            px, py = point
-            # Drills
-            for drill in self.drills:
-                drill['point'] = affinity.rotate(drill['point'], angle, origin=(px, py))
-
-                self.el_count += 1
-                disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
-                if self.old_disp_number < disp_number <= 100:
-                    self.app.proc_container.update_view_text(' %d%%' % disp_number)
-                    self.old_disp_number = disp_number
+            orig = point
 
-            # rotate solid_geometry
-            for tool in self.tools:
-                self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'])
-
-            # Slots
-            for slot in self.slots:
-                slot['stop'] = affinity.rotate(slot['stop'], angle, origin=(px, py))
-                slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py))
+        for tool in self.tools:
+            # Offset Drills
+            if 'drills' in self.tools[tool]:
+                new_drills = []
+                for drill in self.tools[tool]['drills']:
+                    new_drills.append(affinity.rotate(drill, angle, origin=orig))
+                self.tools[tool]['drills'] = new_drills
+
+            # Offset Slots
+            if 'slots' in self.tools[tool]:
+                new_slots = []
+                for slot in self.tools[tool]['slots']:
+                    new_start = affinity.rotate(slot[0], angle, origin=orig)
+                    new_stop = affinity.rotate(slot[1], angle, origin=orig)
+                    new_slot = (new_start, new_stop)
+                    new_slots.append(new_slot)
+
+            # Offset solid_geometry
+            self.tools[tool]['solid_geometry'] = rotate_geom(self.tools[tool]['solid_geometry'], origin=orig)
+
+            # update status display
+            self.el_count += 1
+            disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
+            if self.old_disp_number < disp_number <= 100:
+                self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                self.old_disp_number = disp_number
 
         self.create_geometry()
         self.app.proc_container.new_text = ''
@@ -1534,8 +1578,8 @@ class Excellon(Geometry):
             except TypeError:
                 self.tools[tool]['solid_geometry'] = [res]
             if factor is None:
-                self.tools[tool]['C'] += distance
+                self.tools[tool]['tooldia'] += distance
             else:
-                self.tools[tool]['C'] *= distance
+                self.tools[tool]['tooldia'] *= distance
 
         self.create_geometry()

+ 6 - 11
appTools/ToolDblSided.py

@@ -613,9 +613,9 @@ class DblSidedTool(AppTool):
             return
 
         tools = {}
-        tools["1"] = {}
-        tools["1"]["C"] = dia
-        tools["1"]['solid_geometry'] = []
+        tools[1] = {}
+        tools[1]["tooldia"] = dia
+        tools[1]['solid_geometry'] = []
 
         # holes = self.alignment_holes.get_value()
         holes = eval('[{}]'.format(self.alignment_holes.text()))
@@ -624,21 +624,16 @@ class DblSidedTool(AppTool):
                                                           "Add them and retry."))
             return
 
-        drills = []
-
         for hole in holes:
             point = Point(hole)
             point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
 
-            drills.append({"point": point, "tool": "1"})
-            drills.append({"point": point_mirror, "tool": "1"})
-
-            tools["1"]['solid_geometry'].append(point)
-            tools["1"]['solid_geometry'].append(point_mirror)
+            tools[1]['drills'] = [point, point_mirror]
+            tools[1]['solid_geometry'].append(point)
+            tools[1]['solid_geometry'].append(point_mirror)
 
         def obj_init(obj_inst, app_inst):
             obj_inst.tools = tools
-            obj_inst.drills = drills
             obj_inst.create_geometry()
             obj_inst.source_file = app_inst.export_excellon(obj_name=obj_inst.options['name'], local_use=obj_inst,
                                                             filename=None, use_thread=False)

+ 33 - 58
appTools/ToolDrilling.py

@@ -172,7 +172,6 @@ class ToolDrilling(AppTool, Excellon):
         # ############################ SIGNALS ########################################
         # #############################################################################
 
-        self.ui.object_combo.currentIndexChanged.connect(self.on_object_changed)
         self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
         self.ui.generate_cnc_button.clicked.connect(self.on_cnc_button_click)
         self.ui.tools_table.drag_drop_sig.connect(self.rebuild_ui)
@@ -365,8 +364,6 @@ class ToolDrilling(AppTool, Excellon):
             if opt_key.find('geometry_') == 0:
                 self.default_data[opt_key] = deepcopy(opt_val)
 
-        self.on_object_changed()
-
         self.obj_name = ""
         self.excellon_obj = None
 
@@ -382,6 +379,16 @@ class ToolDrilling(AppTool, Excellon):
         self.ui.operation_radio.set_value("drill")
         self.ui.operation_radio.setEnabled(False)
 
+        self.on_object_changed()
+        if self.excellon_obj:
+            self.build_ui()
+
+        try:
+            self.ui.object_combo.currentIndexChanged.disconnect()
+        except (AttributeError, TypeError):
+            pass
+        self.ui.object_combo.currentIndexChanged.connect(self.on_object_changed)
+
     def rebuild_ui(self):
         # read the table tools uid
         current_uid_list = []
@@ -419,17 +426,8 @@ class ToolDrilling(AppTool, Excellon):
         if self.excellon_obj:
             self.ui.exc_param_frame.setDisabled(False)
 
-            sort = []
-            for k, v in list(self.excellon_obj.tools.items()):
-                sort.append((k, float('%.*f' % (self.decimals, float(v.get('C'))))))
-            order = self.ui.order_radio.get_value()
-            if order == 'fwd':
-                sorted_tools = sorted(sort, key=lambda t1: t1[1])
-            elif order == 'rev':
-                sorted_tools = sorted(sort, key=lambda t1: t1[1], reverse=True)
-            else:
-                sorted_tools = sort
-            tools = [i[0] for i in sorted_tools]
+            tools = [k for k in self.excellon_tools]
+
         else:
             tools = []
 
@@ -438,30 +436,23 @@ class ToolDrilling(AppTool, Excellon):
         self.ui.tools_table.setRowCount(n + 2)
         self.tool_row = 0
 
-        new_options = {}
-        if self.excellon_obj:
-            for opt in self.excellon_obj.options:
-                new_options[opt] = self.excellon_obj.options[opt]
-
         for tool_no in tools:
-            # add the data dictionary for each tool with the default values
-            self.tools[tool_no]['data'] = deepcopy(new_options)
 
             drill_cnt = 0  # variable to store the nr of drills per tool
             slot_cnt = 0  # variable to store the nr of slots per tool
 
             # Find no of drills for the current tool
-            for drill in self.drills:
-                if drill['tool'] == tool_no:
-                    drill_cnt += 1
-
+            try:
+                drill_cnt = len(self.excellon_tools[tool_no]["drills"])
+            except KeyError:
+                drill_cnt = 0
             self.tot_drill_cnt += drill_cnt
 
             # Find no of slots for the current tool
-            for slot in self.slots:
-                if slot['tool'] == tool_no:
-                    slot_cnt += 1
-
+            try:
+                slot_cnt = len(self.excellon_tools[tool_no]["slots"])
+            except KeyError:
+                slot_cnt = 0
             self.tot_slot_cnt += slot_cnt
 
             # Tool name/id
@@ -470,7 +461,7 @@ class ToolDrilling(AppTool, Excellon):
             self.ui.tools_table.setItem(self.tool_row, 0, exc_id_item)
 
             # Tool Diameter
-            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['C']))
+            dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.excellon_tools[tool_no]['tooldia']))
             dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
             self.ui.tools_table.setItem(self.tool_row, 1, dia_item)
 
@@ -496,6 +487,8 @@ class ToolDrilling(AppTool, Excellon):
         # add a last row with the Total number of drills
         empty_1 = QtWidgets.QTableWidgetItem('')
         empty_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
+        empty_1_1 = QtWidgets.QTableWidgetItem('')
+        empty_1_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
 
         label_tot_drill_count = QtWidgets.QTableWidgetItem(_('Total Drills'))
         label_tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled)
@@ -506,6 +499,7 @@ class ToolDrilling(AppTool, Excellon):
         self.ui.tools_table.setItem(self.tool_row, 0, empty_1)
         self.ui.tools_table.setItem(self.tool_row, 1, label_tot_drill_count)
         self.ui.tools_table.setItem(self.tool_row, 2, tot_drill_count)  # Total number of drills
+        self.ui.tools_table.setItem(self.tool_row, 4, empty_1_1)
 
         font = QtGui.QFont()
         font.setBold(True)
@@ -642,35 +636,15 @@ class ToolDrilling(AppTool, Excellon):
             self.excellon_obj = self.app.collection.get_by_name(self.obj_name)
         except Exception as e:
             self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(self.obj_name)))
-            return "Could not retrieve object: %s with error: %s" % (self.obj_name, str(e))
+            return
 
         if self.excellon_obj is None:
             self.ui.exc_param_frame.setDisabled(True)
         else:
             self.ui.exc_param_frame.setDisabled(False)
-            sort = []
-            for k, v in list(self.excellon_obj.tools.items()):
-                sort.append((k, float('%.*f' % (self.decimals, float(v.get('C'))))))
-            dias = [i[0] for i in sort]
+            self.excellon_tools = self.excellon_obj.tools
 
-            if not dias:
-                log.error("At least one tool diameter needed. Excellon object might be empty.")
-                return
-
-            tooluid = 0
-
-            self.excellon_tools.clear()
-            for tool_dia in dias:
-                tooluid += 1
-                self.excellon_tools.update({
-                    int(tooluid): {
-                        'tooldia': float('%.*f' % (self.decimals, tool_dia)),
-
-                        'data': deepcopy(self.default_data),
-                        'solid_geometry': []
-                    }
-                })
-        self.build_ui()
+            self.build_ui()
 
     def ui_connect(self):
 
@@ -809,7 +783,7 @@ class ToolDrilling(AppTool, Excellon):
                 item = self.ui.tools_table.item(c_row, 3)
                 if type(item) is not None:
                     tooluid = item.text()
-                    self.storage_to_form(self.drilling_tools[str(tooluid)]['data'])
+                    self.storage_to_form(self.excellon_tools[str(tooluid)]['data'])
                 else:
                     self.blockSignals(False)
                     return
@@ -845,8 +819,9 @@ class ToolDrilling(AppTool, Excellon):
         :return:    None
         :rtype:
         """
-        if self.ui.tools_table.rowCount() == 0:
+        if self.ui.tools_table.rowCount() == 2:
             # there is no tool in tool table so we can't save the GUI elements values to storage
+            # Excellon Tool Table has 2 rows by default
             return
 
         self.blockSignals(True)
@@ -862,7 +837,7 @@ class ToolDrilling(AppTool, Excellon):
                 row = 0
             tooluid_item = int(self.ui.tools_table.item(row, 3).text())
 
-            for tooluid_key, tooluid_val in self.iso_tools.items():
+            for tooluid_key, tooluid_val in self.excellon_tools.items():
                 if int(tooluid_key) == tooluid_item:
                     new_option_value = self.form_fields[option_changed].get_value()
                     if option_changed in tooluid_val:
@@ -1034,7 +1009,7 @@ class ToolDrilling(AppTool, Excellon):
 
         sort = []
         for k, v in self.tools.items():
-            sort.append((k, v.get('C')))
+            sort.append((k, v.get('tooldia')))
         sorted_tools = sorted(sort, key=lambda t1: t1[1])
 
         if tools == "all":
@@ -1150,7 +1125,7 @@ class ToolDrilling(AppTool, Excellon):
 
         sort = []
         for k, v in self.tools.items():
-            sort.append((k, v.get('C')))
+            sort.append((k, v.get('tooldia')))
         sorted_tools = sorted(sort, key=lambda t1: t1[1])
 
         if tools == "all":

+ 2 - 2
appTools/ToolExtractDrills.py

@@ -530,7 +530,7 @@ class ToolExtractDrills(AppTool):
 
                 tool_in_drills = False
                 for tool, tool_val in tools.items():
-                    if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
+                    if abs(float('%.*f' % (self.decimals, tool_val["tooldia"])) - float('%.*f' % (self.decimals, dia))) < \
                             (10 ** -self.decimals):
                         tool_in_drills = tool
 
@@ -615,7 +615,7 @@ class ToolExtractDrills(AppTool):
 
                 tool_in_drills = False
                 for tool, tool_val in tools.items():
-                    if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
+                    if abs(float('%.*f' % (self.decimals, tool_val["tooldia"])) - float('%.*f' % (self.decimals, dia))) < \
                             (10 ** -self.decimals):
                         tool_in_drills = tool
 

+ 5 - 7
appTools/ToolProperties.py

@@ -397,16 +397,14 @@ class Properties(AppTool):
                 slot_cnt = 0  # variable to store the nr of slots per tool
 
                 # Find no of drills for the current tool
-                for drill in obj.drills:
-                    if drill['tool'] == tool:
-                        drill_cnt += 1
+                if 'drills' in value and value['drills']:
+                    drill_cnt = len(value['drills'])
 
                 tot_drill_cnt += drill_cnt
 
                 # Find no of slots for the current tool
-                for slot in obj.slots:
-                    if slot['tool'] == tool:
-                        slot_cnt += 1
+                if 'slots' in value and value['slots']:
+                    slot_cnt = len(value['slots'])
 
                 tot_slot_cnt += slot_cnt
 
@@ -414,7 +412,7 @@ class Properties(AppTool):
                     toolid,
                     [
                         _('Diameter'),
-                        '%.*f %s' % (self.decimals, value['C'], self.app.defaults['units'].lower())
+                        '%.*f %s' % (self.decimals, value['tooldia'], self.app.defaults['units'].lower())
                     ],
                     True
                 )

+ 1 - 1
appTools/ToolPunchGerber.py

@@ -558,7 +558,7 @@ class ToolPunchGerber(AppTool):
                     # make it work only for Gerber Flashes who are Points in 'follow'
                     if 'solid' in elem and isinstance(elem['follow'], Point):
                         for drill in exc_obj.drills:
-                            clear_apid_size = exc_obj.tools[drill['tool']]['C']
+                            clear_apid_size = exc_obj.tools[drill['tool']]['tooldia']
 
                             # since there may be drills that do not drill into a pad we test only for drills in a pad
                             if drill['point'].within(elem['solid']):

+ 32 - 15
app_Main.py

@@ -2314,7 +2314,20 @@ class App(QtCore.QObject):
                         self.ui.tool_scroll_area.takeWidget()
 
                         # delete the old object (the source object) if it was an empty one
-                        if len(edited_obj.drills) == 0 and len(edited_obj.slots) == 0:
+                        # find if we have drills:
+                        has_drills = None
+                        for tt in edited_obj.tools:
+                            if 'drills' in edited_obj.tools[tt] and edited_obj.tools[tt]['drills']:
+                                has_drills = True
+                                break
+                        # find if we have slots:
+                        has_slots = None
+                        for tt in edited_obj.tools:
+                            if 'slots' in edited_obj.tools[tt] and edited_obj.tools[tt]['slots']:
+                                has_slots = True
+                                slots_in_file = 1
+                                break
+                        if has_drills is None and has_slots is None:
                             old_name = edited_obj.options['name']
                             self.collection.delete_by_name(name=old_name)
                         self.inform.emit('[success] %s' % _("Editor exited. Editor content saved."))
@@ -8230,11 +8243,11 @@ class App(QtCore.QObject):
 
                     for tool in obj.tools:
                         if eunits == 'METRIC':
-                            header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
+                            header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['tooldia']) * factor,
                                                                           tool=str(tool),
                                                                           dec=2)
                         else:
-                            header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
+                            header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['tooldia']) * factor,
                                                                           tool=str(tool),
                                                                           dec=4)
                 else:
@@ -8247,13 +8260,15 @@ class App(QtCore.QObject):
 
                         for tool in obj.tools:
                             if eunits == 'METRIC':
-                                header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
-                                                                              tool=str(tool),
-                                                                              dec=2)
+                                header += "T{tool}F00S00C{:.{dec}f}\n".format(
+                                    float(obj.tools[tool]['tooldia']) * factor,
+                                    tool=str(tool),
+                                    dec=2)
                             else:
-                                header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
-                                                                              tool=str(tool),
-                                                                              dec=4)
+                                header += "T{tool}F00S00C{:.{dec}f}\n".format(
+                                    float(obj.tools[tool]['tooldia']) * factor,
+                                    tool=str(tool),
+                                    dec=4)
                     else:
                         has_slots, excellon_code = obj.export_excellon(ewhole, efract,
                                                                        form='ndec', e_zeros='TZ', factor=factor,
@@ -8263,13 +8278,15 @@ class App(QtCore.QObject):
 
                         for tool in obj.tools:
                             if eunits == 'METRIC':
-                                header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
-                                                                              tool=str(tool),
-                                                                              dec=2)
+                                header += "T{tool}F00S00C{:.{dec}f}\n".format(
+                                    float(obj.tools[tool]['tooldia']) * factor,
+                                    tool=str(tool),
+                                    dec=2)
                             else:
-                                header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['C']) * factor,
-                                                                              tool=str(tool),
-                                                                              dec=4)
+                                header += "T{tool}F00S00C{:.{dec}f}\n".format(
+                                    float(obj.tools[tool]['tooldia']) * factor,
+                                    tool=str(tool),
+                                    dec=4)
                 header += '%\n'
                 footer = 'M30\n'
 

+ 115 - 89
camlib.py

@@ -2555,8 +2555,6 @@ class CNCjob(Geometry):
         # here store the routing time
         self.routing_time = 0.0
 
-        # used for creating drill CCode geometry; will be updated in the generate_from_excellon_by_tool()
-        self.exc_drills = None
         # store here the Excellon source object tools to be accessible locally
         self.exc_tools = None
 
@@ -2710,8 +2708,7 @@ class CNCjob(Geometry):
         :rtype:         None
         """
 
-        # create a local copy of the exobj.drills so it can be used for creating drill CCode geometry
-        self.exc_drills = deepcopy(exobj.drills)
+        # create a local copy of the exobj.tools so it can be used for creating drill CCode geometry
         self.exc_tools = deepcopy(exobj.tools)
 
         # the Excellon GCode preprocessor will use this info in the start_code() method
@@ -2774,8 +2771,8 @@ class CNCjob(Geometry):
         # sorted_tools = sorted(exobj.tools.items(), key=lambda t1: t1['C'])
 
         sort = []
-        for k, v in list(exobj.tools.items()):
-            sort.append((k, v.get('C')))
+        for k, v in list(self.exc_tools.items()):
+            sort.append((int(k), v['tooldia']))
         sorted_tools = sorted(sort, key=lambda t1: t1[1])
 
         if tools == "all":
@@ -2783,7 +2780,7 @@ class CNCjob(Geometry):
             log.debug("Tools 'all' and sorted are: %s" % str(tools))
         else:
             selected_tools = [x.strip() for x in tools.split(",")]  # we strip spaces and also separate the tools by ','
-            selected_tools = [t1 for t1 in selected_tools if t1 in selected_tools]
+            selected_tools = [int(t1) for t1 in selected_tools if t1 in selected_tools]
 
             # Create a sorted list of selected tools from the sorted_tools list
             tools = [i for i, j in sorted_tools for k in selected_tools if i == k]
@@ -2803,18 +2800,20 @@ class CNCjob(Geometry):
         for it in sorted_tools:
             for to_ol in tools:
                 if to_ol == it[0]:
-                    drill_no = 0
                     sol_geo = []
-                    for dr in exobj.drills:
-                        if dr['tool'] == it[0]:
-                            drill_no += 1
-                            sol_geo.append(dr['point'])
+
+                    drill_no = 0
+                    if 'drills' in exobj.tools[to_ol]:
+                        drill_no = len(exobj.tools[to_ol]['drills'])
+                        for drill in exobj.tools[to_ol]['drills']:
+                            sol_geo.append(drill.buffer((it[1] / 2.0), resolution=self.geo_steps_per_circle))
+
                     slot_no = 0
-                    for dr in exobj.slots:
-                        if dr['tool'] == it[0]:
-                            slot_no += 1
-                            start = (dr['start'].x, dr['start'].y)
-                            stop = (dr['stop'].x, dr['stop'].y)
+                    if 'slots' in exobj.tools[to_ol]:
+                        slot_no = len(exobj.tools[to_ol]['slots'])
+                        for slot in exobj.tools[to_ol]['slots']:
+                            start = (slot[0].x, slot[0].y)
+                            stop = (slot[1].x, slot[1].y)
                             sol_geo.append(
                                 LineString([start, stop]).buffer((it[1] / 2.0), resolution=self.geo_steps_per_circle)
                             )
@@ -2850,25 +2849,26 @@ class CNCjob(Geometry):
 
         # Points (Group by tool): a dictionary of shapely Point geo elements grouped by tool number
         points = {}
-        for drill in exobj.drills:
+        for tool, tool_dict in self.exc_tools.items():
             if self.app.abort_flag:
                 # graceful abort requested by the user
                 raise grace
 
-            if drill['tool'] in tools:
-                try:
-                    points[drill['tool']].append(drill['point'])
-                except KeyError:
-                    points[drill['tool']] = [drill['point']]
+            if 'drills' in tool_dict and tool_dict['drills']:
+                for drill_pt in tool_dict['drills']:
+                    try:
+                        points[tool].append(drill_pt)
+                    except KeyError:
+                        points[tool] = [drill_pt]
 
-        # log.debug("Found %d drills." % len(points))
+        log.debug("Found %d TOOLS." % len(points))
 
         # check if there are drill points in the exclusion areas.
         # If we find any within the exclusion areas return 'fail'
         for tool in points:
             for pt in points[tool]:
                 for area in self.app.exc_areas.exclusion_areas_storage:
-                    pt_buf = pt.buffer(exobj.tools[tool]['C'] / 2.0)
+                    pt_buf = pt.buffer(self.exc_tools[tool]['tooldia'] / 2.0)
                     if pt_buf.within(area['shape']) or pt_buf.intersects(area['shape']):
                         self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed. Drill points inside the exclusion zones."))
                         return 'fail'
@@ -2954,22 +2954,28 @@ class CNCjob(Geometry):
             used_excellon_optimization_type = self.excellon_optimization_type
             if used_excellon_optimization_type == 'M':
                 log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.")
-                if exobj.drills:
+
+                has_drills = None
+                for tool, tool_dict in self.exc_tools.items():
+                    if 'drills' in tool_dict and tool_dict['drills']:
+                        has_drills = True
+                        break
+                if has_drills:
                     for tool in tools:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             raise grace
 
                         self.tool = tool
-                        self.postdata['toolC'] = exobj.tools[tool]["C"]
-                        self.tooldia = exobj.tools[tool]["C"]
+                        self.tooldia = self.exc_tools[tool]["tooldia"]
+                        self.postdata['toolC'] = self.tooldia
 
                         if self.use_ui:
-                            self.z_feedrate = exobj.tools[tool]['data']['feedrate_z']
-                            self.feedrate = exobj.tools[tool]['data']['feedrate']
+                            self.z_feedrate = self.exc_tools[tool]['data']['feedrate_z']
+                            self.feedrate = self.exc_tools[tool]['data']['feedrate']
                             gcode += self.doformat(p.z_feedrate_code)
 
-                            self.z_cut = exobj.tools[tool]['data']['cutz']
+                            self.z_cut = self.exc_tools[tool]['data']['cutz']
 
                             if self.machinist_setting == 0:
                                 if self.z_cut > 0:
@@ -2991,12 +2997,12 @@ class CNCjob(Geometry):
 
                             old_zcut = deepcopy(self.z_cut)
 
-                            self.z_move = exobj.tools[tool]['data']['travelz']
-                            self.spindlespeed = exobj.tools[tool]['data']['spindlespeed']
-                            self.dwell = exobj.tools[tool]['data']['dwell']
-                            self.dwelltime = exobj.tools[tool]['data']['dwelltime']
-                            self.multidepth = exobj.tools[tool]['data']['multidepth']
-                            self.z_depthpercut = exobj.tools[tool]['data']['depthperpass']
+                            self.z_move = self.exc_tools[tool]['data']['travelz']
+                            self.spindlespeed = self.exc_tools[tool]['data']['spindlespeed']
+                            self.dwell = self.exc_tools[tool]['data']['dwell']
+                            self.dwelltime = self.exc_tools[tool]['data']['dwelltime']
+                            self.multidepth = self.exc_tools[tool]['data']['multidepth']
+                            self.z_depthpercut = self.exc_tools[tool]['data']['depthperpass']
                         else:
                             old_zcut = deepcopy(self.z_cut)
 
@@ -3085,7 +3091,7 @@ class CNCjob(Geometry):
                                 if self.dwell is True:
                                     gcode += self.doformat(p.dwell_code)  # Dwell time
 
-                            current_tooldia = float('%.*f' % (self.decimals, float(exobj.tools[tool]["C"])))
+                            current_tooldia = float('%.*f' % (self.decimals, float(self.exc_tools[tool]["tooldia"])))
 
                             self.app.inform.emit(
                                 '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
@@ -3098,7 +3104,7 @@ class CNCjob(Geometry):
                             # because the values for Z offset are created in build_ui()
                             # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                             try:
-                                z_offset = float(exobj.tools[tool]['data']['offset']) * (-1)
+                                z_offset = float(self.exc_tools[tool]['data']['offset']) * (-1)
                             except KeyError:
                                 z_offset = 0
                             self.z_cut = z_offset + old_zcut
@@ -3138,7 +3144,7 @@ class CNCjob(Geometry):
                                             gcode += self.doformat(p.lift_code, x=locx, y=locy)
 
                                             # restore z_move
-                                            self.z_move = exobj.tools[tool]['data']['travelz']
+                                            self.z_move = self.exc_tools[tool]['data']['travelz']
                                         else:
                                             if prev_z is not None:
                                                 # move to next point
@@ -3146,7 +3152,7 @@ class CNCjob(Geometry):
 
                                                 # we assume that previously the z_move was altered therefore raise to
                                                 # the travel_z (z_move)
-                                                self.z_move = exobj.tools[tool]['data']['travelz']
+                                                self.z_move = self.exc_tools[tool]['data']['travelz']
                                                 gcode += self.doformat(p.lift_code, x=locx, y=locy)
                                             else:
                                                 # move to next point
@@ -3218,21 +3224,28 @@ class CNCjob(Geometry):
 
             if used_excellon_optimization_type == 'B':
                 log.debug("Using OR-Tools Basic drill path optimization.")
-                if exobj.drills:
+
+                has_drills = None
+                for tool, tool_dict in self.exc_tools.items():
+                    if 'drills' in tool_dict and tool_dict['drills']:
+                        has_drills = True
+                        break
+
+                if has_drills:
                     for tool in tools:
                         if self.app.abort_flag:
                             # graceful abort requested by the user
                             raise grace
 
                         self.tool = tool
-                        self.postdata['toolC'] = exobj.tools[tool]["C"]
-                        self.tooldia = exobj.tools[tool]["C"]
+                        self.tooldia = self.exc_tools[tool]["tooldia"]
+                        self.postdata['toolC'] = self.tooldia
 
                         if self.use_ui:
-                            self.z_feedrate = exobj.tools[tool]['data']['feedrate_z']
-                            self.feedrate = exobj.tools[tool]['data']['feedrate']
+                            self.z_feedrate = self.exc_tools[tool]['data']['feedrate_z']
+                            self.feedrate = self.exc_tools[tool]['data']['feedrate']
                             gcode += self.doformat(p.z_feedrate_code)
-                            self.z_cut = exobj.tools[tool]['data']['cutz']
+                            self.z_cut = self.exc_tools[tool]['data']['cutz']
 
                             if self.machinist_setting == 0:
                                 if self.z_cut > 0:
@@ -3254,13 +3267,13 @@ class CNCjob(Geometry):
 
                             old_zcut = deepcopy(self.z_cut)
 
-                            self.z_move = exobj.tools[tool]['data']['travelz']
+                            self.z_move = self.exc_tools[tool]['data']['travelz']
 
-                            self.spindlespeed = exobj.tools[tool]['data']['spindlespeed']
-                            self.dwell = exobj.tools[tool]['data']['dwell']
-                            self.dwelltime = exobj.tools[tool]['data']['dwelltime']
-                            self.multidepth = exobj.tools[tool]['data']['multidepth']
-                            self.z_depthpercut = exobj.tools[tool]['data']['depthperpass']
+                            self.spindlespeed = self.exc_tools[tool]['data']['spindlespeed']
+                            self.dwell = self.exc_tools[tool]['data']['dwell']
+                            self.dwelltime = self.exc_tools[tool]['data']['dwelltime']
+                            self.multidepth = self.exc_tools[tool]['data']['multidepth']
+                            self.z_depthpercut = self.exc_tools[tool]['data']['depthperpass']
                         else:
                             old_zcut = deepcopy(self.z_cut)
 
@@ -3338,7 +3351,7 @@ class CNCjob(Geometry):
                                 if self.dwell is True:
                                     gcode += self.doformat(p.dwell_code)  # Dwell time
 
-                            current_tooldia = float('%.*f' % (self.decimals, float(exobj.tools[tool]["C"])))
+                            current_tooldia = float('%.*f' % (self.decimals, float(self.exc_tools[tool]["tooldia"])))
 
                             self.app.inform.emit(
                                 '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
@@ -3351,7 +3364,7 @@ class CNCjob(Geometry):
                             # because the values for Z offset are created in build_ui()
                             # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                             try:
-                                z_offset = float(exobj.tools[tool]['data']['offset']) * (-1)
+                                z_offset = float(self.exc_tools[tool]['data']['offset']) * (-1)
                             except KeyError:
                                 z_offset = 0
                             self.z_cut = z_offset + old_zcut
@@ -3390,7 +3403,7 @@ class CNCjob(Geometry):
                                             gcode += self.doformat(p.lift_code, x=locx, y=locy)
 
                                             # restore z_move
-                                            self.z_move = exobj.tools[tool]['data']['travelz']
+                                            self.z_move = self.exc_tools[tool]['data']['travelz']
                                         else:
                                             if prev_z is not None:
                                                 # move to next point
@@ -3398,7 +3411,7 @@ class CNCjob(Geometry):
 
                                                 # we assume that previously the z_move was altered therefore raise to
                                                 # the travel_z (z_move)
-                                                self.z_move = exobj.tools[tool]['data']['travelz']
+                                                self.z_move = self.exc_tools[tool]['data']['travelz']
                                                 gcode += self.doformat(p.lift_code, x=locx, y=locy)
                                             else:
                                                 # move to next point
@@ -3473,22 +3486,29 @@ class CNCjob(Geometry):
 
         if used_excellon_optimization_type == 'T':
             log.debug("Using Travelling Salesman drill path optimization.")
+
+            has_drills = None
+            for tool, tool_dict in self.exc_tools.items():
+                if 'drills' in tool_dict and tool_dict['drills']:
+                    has_drills = True
+                    break
+
             for tool in tools:
                 if self.app.abort_flag:
                     # graceful abort requested by the user
                     raise grace
 
-                if exobj.drills:
+                if has_drills:
                     self.tool = tool
-                    self.postdata['toolC'] = exobj.tools[tool]["C"]
-                    self.tooldia = exobj.tools[tool]["C"]
+                    self.tooldia = self.exc_tools[tool]["tooldia"]
+                    self.postdata['toolC'] = self.tooldia
 
                     if self.use_ui:
-                        self.z_feedrate = exobj.tools[tool]['data']['feedrate_z']
-                        self.feedrate = exobj.tools[tool]['data']['feedrate']
+                        self.z_feedrate = self.exc_tools[tool]['data']['feedrate_z']
+                        self.feedrate = self.exc_tools[tool]['data']['feedrate']
                         gcode += self.doformat(p.z_feedrate_code)
 
-                        self.z_cut = exobj.tools[tool]['data']['cutz']
+                        self.z_cut = self.exc_tools[tool]['data']['cutz']
 
                         if self.machinist_setting == 0:
                             if self.z_cut > 0:
@@ -3510,12 +3530,12 @@ class CNCjob(Geometry):
 
                         old_zcut = deepcopy(self.z_cut)
 
-                        self.z_move = exobj.tools[tool]['data']['travelz']
-                        self.spindlespeed = exobj.tools[tool]['data']['spindlespeed']
-                        self.dwell = exobj.tools[tool]['data']['dwell']
-                        self.dwelltime = exobj.tools[tool]['data']['dwelltime']
-                        self.multidepth = exobj.tools[tool]['data']['multidepth']
-                        self.z_depthpercut = exobj.tools[tool]['data']['depthperpass']
+                        self.z_move = self.exc_tools[tool]['data']['travelz']
+                        self.spindlespeed = self.exc_tools[tool]['data']['spindlespeed']
+                        self.dwell = self.exc_tools[tool]['data']['dwell']
+                        self.dwelltime = self.exc_tools[tool]['data']['dwelltime']
+                        self.multidepth = self.exc_tools[tool]['data']['multidepth']
+                        self.z_depthpercut = self.exc_tools[tool]['data']['depthperpass']
                     else:
                         old_zcut = deepcopy(self.z_cut)
 
@@ -3536,7 +3556,7 @@ class CNCjob(Geometry):
                             if self.dwell is True:
                                 gcode += self.doformat(p.dwell_code)  # Dwell time
 
-                        current_tooldia = float('%.*f' % (self.decimals, float(exobj.tools[tool]["C"])))
+                        current_tooldia = float('%.*f' % (self.decimals, float(self.exc_tools[tool]["tooldia"])))
 
                         self.app.inform.emit(
                             '%s: %s%s.' % (_("Starting G-Code for tool with diameter"),
@@ -3549,7 +3569,7 @@ class CNCjob(Geometry):
                         # because the values for Z offset are created in build_ui()
                         # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                         try:
-                            z_offset = float(exobj.tools[tool]['data']['offset']) * (-1)
+                            z_offset = float(self.exc_tools[tool]['data']['offset']) * (-1)
                         except KeyError:
                             z_offset = 0
                         self.z_cut = z_offset + old_zcut
@@ -3593,7 +3613,7 @@ class CNCjob(Geometry):
                                         gcode += self.doformat(p.lift_code, x=locx, y=locy)
 
                                         # restore z_move
-                                        self.z_move = exobj.tools[tool]['data']['travelz']
+                                        self.z_move = self.exc_tools[tool]['data']['travelz']
                                     else:
                                         if prev_z is not None:
                                             # move to next point
@@ -3601,7 +3621,7 @@ class CNCjob(Geometry):
 
                                             # we assume that previously the z_move was altered therefore raise to
                                             # the travel_z (z_move)
-                                            self.z_move = exobj.tools[tool]['data']['travelz']
+                                            self.z_move = self.exc_tools[tool]['data']['travelz']
                                             gcode += self.doformat(p.lift_code, x=locx, y=locy)
                                         else:
                                             # move to next point
@@ -3687,6 +3707,7 @@ class CNCjob(Geometry):
         self.routing_time += lift_time + traveled_time
 
         self.gcode = gcode
+
         self.app.inform.emit(_("Finished G-Code generation..."))
         return 'OK'
 
@@ -4914,22 +4935,27 @@ class CNCjob(Geometry):
                         )
 
                         # find the drill diameter knowing the drill coordinates
-                        for pt_dict in self.exc_drills:
-                            point_in_dict_coords = (
-                                float('%.*f' % (self.decimals, pt_dict['point'].x)),
-                                float('%.*f' % (self.decimals, pt_dict['point'].y))
-                            )
-                            if point_in_dict_coords == current_drill_point_coords:
-                                tool = pt_dict['tool']
-                                dia = self.exc_tools[tool]['C']
-                                kind = ['C', 'F']
-                                geometry.append(
-                                    {
-                                        "geom": Point(current_drill_point_coords).buffer(dia / 2.0).exterior,
-                                        "kind": kind
-                                    }
-                                )
-                                break
+                        break_loop = False
+                        for tool, tool_dict in self.exc_tools.items():
+                            if 'drills' in tool_dict:
+                                for drill_pt in tool_dict['drills']:
+                                    point_in_dict_coords = (
+                                        float('%.*f' % (self.decimals, drill_pt.x)),
+                                        float('%.*f' % (self.decimals, drill_pt.y))
+                                    )
+                                    if point_in_dict_coords == current_drill_point_coords:
+                                        dia = self.exc_tools[tool]['tooldia']
+                                        kind = ['C', 'F']
+                                        geometry.append(
+                                            {
+                                                "geom": Point(current_drill_point_coords).buffer(dia / 2.0).exterior,
+                                                "kind": kind
+                                            }
+                                        )
+                                        break_loop = True
+                                        break
+                                if break_loop:
+                                    break
 
             if 'G' in gobj:
                 current['G'] = int(gobj['G'])

+ 1 - 1
preprocessors/Berta_CNC.py

@@ -48,7 +48,7 @@ class Berta_CNC(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/ISEL_CNC.py

@@ -38,7 +38,7 @@ class ISEL_CNC(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/ISEL_ICP_CNC.py

@@ -38,7 +38,7 @@ class ISEL_ICP_CNC(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n;TOOLS DIAMETER: \n'
             for tool, val in p['exc_tools'].items():
-                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + '\n'
+                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + '\n'
 
             gcode += '\n;FEEDRATE Z: \n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/Marlin.py

@@ -40,7 +40,7 @@ class Marlin(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n;TOOLS DIAMETER: \n'
             for tool, val in p['exc_tools'].items():
-                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + '\n'
+                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + '\n'
 
             gcode += '\n;FEEDRATE Z: \n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/Repetier.py

@@ -40,7 +40,7 @@ class Repetier(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n;TOOLS DIAMETER: \n'
             for tool, val in p['exc_tools'].items():
-                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + '\n'
+                gcode += ';Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + '\n'
 
             gcode += '\n;FEEDRATE Z: \n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/Toolchange_Custom.py

@@ -39,7 +39,7 @@ class Toolchange_Custom(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/Toolchange_Manual.py

@@ -39,7 +39,7 @@ class Toolchange_Manual(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/Toolchange_Probe_MACH3.py

@@ -39,7 +39,7 @@ class Toolchange_Probe_MACH3(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/default.py

@@ -40,7 +40,7 @@ class default(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/grbl_11.py

@@ -40,7 +40,7 @@ class grbl_11(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 1 - 1
preprocessors/line_xyz.py

@@ -39,7 +39,7 @@ class line_xyz(PreProc):
         elif str(p['options']['type']) == 'Excellon' and p['use_ui'] is True:
             gcode += '\n(TOOLS DIAMETER: )\n'
             for tool, val in p['exc_tools'].items():
-                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["C"]) + ')\n'
+                gcode += '(Tool: %s -> ' % str(tool) + 'Dia: %s' % str(val["tooldia"]) + ')\n'
 
             gcode += '\n(FEEDRATE Z: )\n'
             for tool, val in p['exc_tools'].items():

+ 6 - 8
tclCommands/TclCommandDrillcncjob.py

@@ -198,25 +198,23 @@ class TclCommandDrillcncjob(TclCommandSignaled):
             if tools == 'all':
                 sort = []
                 for k, v in list(obj.tools.items()):
-                    sort.append((k, v.get('C')))
+                    sort.append((k, v.get('tooldia')))
                 sorted_tools = sorted(sort, key=lambda t1: t1[1])
                 use_tools = [i[0] for i in sorted_tools]
 
                 for tool_no in use_tools:
-                    tool_dia_used = obj.tools[tool_no]['C']
+                    tool_dia_used = obj.tools[tool_no]['tooldia']
 
                     drill_cnt = 0  # variable to store the nr of drills per tool
                     slot_cnt = 0  # variable to store the nr of slots per tool
 
                     # Find no of drills for the current tool
-                    for drill in obj.drills:
-                        if drill['tool'] == tool_no:
-                            drill_cnt += 1
+                    if 'drills' in obj.tools[tool_no] and obj.tools[tool_no]['drills']:
+                        drill_cnt = len(obj.tools[tool_no]['drills'])
 
                     # Find no of slots for the current tool
-                    for slot in obj.slots:
-                        if slot['tool'] == tool_no:
-                            slot_cnt += 1
+                    if 'slots' in obj.tools[tool_no] and obj.tools[tool_no]['slots']:
+                        slot_cnt = len(obj.tools[tool_no]['slots'])
 
                     used_tools_info.append([str(tool_no), str(tool_dia_used), str(drill_cnt), str(slot_cnt)])