Browse Source

Merged in marius_stanciu/flatcam_beta/Beta (pull request #155)

Beta
Marius Stanciu 6 years ago
parent
commit
aa45b57ee1

+ 64 - 24
FlatCAMApp.py

@@ -94,8 +94,8 @@ class App(QtCore.QObject):
     log.addHandler(handler)
 
     # Version
-    version = 8.918
-    version_date = "2019/06/11"
+    version = 8.919
+    version_date = "2019/06/22"
     beta = True
 
     # current date now
@@ -2108,11 +2108,10 @@ class App(QtCore.QObject):
         self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'))
 
         self.calculator_tool = ToolCalculator(self)
-        self.calculator_tool.install(icon=QtGui.QIcon('share/calculator24.png'))
+        self.calculator_tool.install(icon=QtGui.QIcon('share/calculator24.png'), separator=True)
 
         self.sub_tool = ToolSub(self)
-        self.sub_tool.install(icon=QtGui.QIcon('share/sub32.png'), pos=self.ui.menuedit_convert,
-                              before=self.ui.menuedit_convert_sg2mg)
+        self.sub_tool.install(icon=QtGui.QIcon('share/sub32.png'), pos=self.ui.menutool, separator=True)
 
         self.move_tool = ToolMove(self)
         self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
@@ -2245,7 +2244,18 @@ class App(QtCore.QObject):
                     self.inform.emit(_("[WARNING_NOTCL] Simultanoeus editing of tools geometry in a MultiGeo Geometry "
                                        "is not possible.\n"
                                        "Edit only one geometry at a time."))
-                self.geo_editor.edit_fcgeometry(edited_object, multigeo_tool=edited_tools[0])
+
+                # determine the tool dia of the selected tool
+                selected_tooldia = float(edited_object.ui.geo_tools_table.item((edited_tools[0] - 1), 1).text())
+
+                # now find the key in the edited_object.tools that has this tooldia
+                multi_tool = 1
+                for tool in edited_object.tools:
+                    if edited_object.tools[tool]['tooldia'] == selected_tooldia:
+                        multi_tool = tool
+                        break
+
+                self.geo_editor.edit_fcgeometry(edited_object, multigeo_tool=multi_tool)
             else:
                 self.geo_editor.edit_fcgeometry(edited_object)
 
@@ -3618,9 +3628,10 @@ class App(QtCore.QObject):
         if self.toggle_units_ignore:
             return
 
+        new_units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
+
         # If option is the same, then ignore
-        if self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == \
-                self.defaults["units"].upper():
+        if new_units == self.defaults["units"].upper():
             self.log.debug("on_toggle_units(): Same as defaults, so ignoring.")
             return
 
@@ -3667,16 +3678,24 @@ class App(QtCore.QObject):
                     coords_xy[0] *= sfactor
                     coords_xy[1] *= sfactor
                     self.options['geometry_toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
+                elif dim == 'geometry_cnctooldia':
+                    self.options['geometry_cnctooldia'] = ''
+                    tools_diameters = [float(eval(a)) for a in self.defaults["geometry_cnctooldia"].split(",")]
+                    for t in range(len(tools_diameters)):
+                        tools_diameters[t] *= sfactor
+                        self.options['geometry_cnctooldia'] += "%f, " % tools_diameters[t]
                 elif dim == 'tools_ncctools':
+                    self.options['tools_ncctools'] = ''
                     ncctols = [float(eval(a)) for a in self.defaults["tools_ncctools"].split(",")]
-                    ncctols[0] *= sfactor
-                    ncctols[1] *= sfactor
-                    self.options['tools_ncctools'] = "%f, %f" % (ncctols[0], ncctols[1])
+                    for t in range(len(ncctols)):
+                        ncctols[t] *= sfactor
+                        self.options['tools_ncctools'] += "%f, " % ncctols[t]
                 elif dim == 'tools_solderpaste_tools':
+                    self.options['tools_solderpaste_tools'] = ""
                     sp_tools = [float(eval(a)) for a in self.defaults["tools_solderpaste_tools"].split(",")]
-                    sp_tools[0] *= sfactor
-                    sp_tools[1] *= sfactor
-                    self.options['tools_solderpaste_tools'] = "%f, %f" % (sp_tools[0], sp_tools[1])
+                    for t in range(len(sp_tools)):
+                        sp_tools[t] *= sfactor
+                        self.options['tools_solderpaste_tools'] = "%f, " % sp_tools[t]
                 elif dim == 'tools_solderpaste_xy_toolchange':
                     sp_coords = [float(eval(a)) for a in self.defaults["tools_solderpaste_xy_toolchange"].split(",")]
                     sp_coords[0] *= sfactor
@@ -3700,16 +3719,24 @@ class App(QtCore.QObject):
                     coords_xy[0] *= sfactor
                     coords_xy[1] *= sfactor
                     self.defaults['geometry_toolchangexy'] = "%.4f, %.4f" % (coords_xy[0], coords_xy[1])
+                elif dim == 'geometry_cnctooldia':
+                    self.defaults['geometry_cnctooldia'] = ''
+                    tools_diameters = [float(eval(a)) for a in self.defaults["geometry_cnctooldia"].split(",")]
+                    for t in range(len(tools_diameters)):
+                        tools_diameters[t] *= sfactor
+                        self.defaults['geometry_cnctooldia'] += "%.4f, " % tools_diameters[t]
                 elif dim == 'tools_ncctools':
+                    self.defaults['tools_ncctools'] = ''
                     ncctols = [float(eval(a)) for a in self.defaults["tools_ncctools"].split(",")]
-                    ncctols[0] *= sfactor
-                    ncctols[1] *= sfactor
-                    self.defaults['tools_ncctools'] = "%.4f, %.4f" % (ncctols[0], ncctols[1])
+                    for t in range(len(ncctols)):
+                        ncctols[t] *= sfactor
+                        self.defaults['tools_ncctools'] += "%.4f, " % ncctols[t]
                 elif dim == 'tools_solderpaste_tools':
+                    self.defaults['tools_solderpaste_tools'] = ""
                     sp_tools = [float(eval(a)) for a in self.defaults["tools_solderpaste_tools"].split(",")]
-                    sp_tools[0] *= sfactor
-                    sp_tools[1] *= sfactor
-                    self.defaults['tools_solderpaste_tools'] = "%.4f, %.4f" % (sp_tools[0], sp_tools[1])
+                    for t in range(len(sp_tools)):
+                        sp_tools[t] *= sfactor
+                        self.defaults['tools_solderpaste_tools'] = "%.4f, " % sp_tools[t]
                 elif dim == 'tools_solderpaste_xy_toolchange':
                     sp_coords = [float(eval(a)) for a in self.defaults["tools_solderpaste_xy_toolchange"].split(",")]
                     sp_coords[0] *= sfactor
@@ -3723,7 +3750,7 @@ class App(QtCore.QObject):
 
         # The scaling factor depending on choice of units.
         factor = 1/25.4
-        if self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
+        if new_units == 'MM':
             factor = 25.4
 
         # Changing project units. Warn user.
@@ -3757,8 +3784,12 @@ class App(QtCore.QObject):
                 self.plotcanvas.draw_workspace()
 
             # adjust the grid values on the main toolbar
-            self.ui.grid_gap_x_entry.set_value(float(self.ui.grid_gap_x_entry.get_value()) * factor)
-            self.ui.grid_gap_y_entry.set_value(float(self.ui.grid_gap_y_entry.get_value()) * factor)
+            dec = 6 if new_units == 'IN'else 4
+            val_x = float(self.ui.grid_gap_x_entry.get_value()) * factor
+            self.ui.grid_gap_x_entry.set_value(val_x, decimals=dec)
+            if not self.ui.grid_gap_link_cb.isChecked():
+                val_y = float(self.ui.grid_gap_y_entry.get_value()) * factor
+                self.ui.grid_gap_y_entry.set_value(val_y, decimals=dec)
 
             units = self.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
             for obj in self.collection.get_list():
@@ -5249,6 +5280,7 @@ class App(QtCore.QObject):
         if index.isValid():
             if index.internalPointer().parent_item != self.collection.root_item:
                 self.ui.notebook.setCurrentWidget(self.ui.selected_tab)
+        self.collection.on_item_activated(index)
 
     def grid_status(self):
         if self.ui.grid_snap_btn.isChecked():
@@ -5423,6 +5455,7 @@ class App(QtCore.QObject):
                     self.click_noproject = True
 
                     self.clipboard.setText(self.defaults["global_point_clipboard_format"] % (self.pos[0], self.pos[1]))
+                    self.inform.emit(_("[success] Coordinates copied to clipboard."))
                     return
 
             self.on_mouse_move_over_plot(event, origin_click=True)
@@ -5579,6 +5612,10 @@ class App(QtCore.QObject):
                             if self.command_active is None:
                                 self.select_objects(key='CTRL')
                                 self.delete_hover_shape()
+                        elif modifiers == QtCore.Qt.ShiftModifier:
+                            # if SHIFT was pressed and LMB is clicked then we have a coordinates copy to clipboard
+                            # therefore things should stay as they are
+                            pass
                         else:
                             # If there is no active command (self.command_active is None) then we check if we clicked
                             # on a object by checking the bounding limits against mouse click position
@@ -8252,7 +8289,10 @@ The normal flow when working in FlatCAM is the following:</span></p>
         """
         FlatCAMObj.app = self
         ObjectCollection.app = self
-
+        Gerber.app = self
+        Excellon.app = self
+        Geometry.app = self
+        CNCjob.app = self
         FCProcess.app = self
         FCProcessContainer.app = self
 

+ 99 - 56
FlatCAMObj.py

@@ -68,6 +68,9 @@ class FlatCAMObj(QtCore.QObject):
 
         self.form_fields = {}
 
+        # store here the default data for Geometry Data
+        self.default_data = {}
+
         self.kind = None  # Override with proper name
 
         # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene)
@@ -137,7 +140,7 @@ class FlatCAMObj(QtCore.QObject):
         if key == 'plot':
             self.visible = self.options['plot']
 
-        # self.optionChanged.emit(key)
+        self.optionChanged.emit(key)
 
     def set_ui(self, ui):
         self.ui = ui
@@ -199,6 +202,8 @@ class FlatCAMObj(QtCore.QObject):
                 log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list")
 
             self.options["name"] = self.ui.name_entry.get_value()
+            self.default_data["name"] = self.ui.name_entry.get_value()
+            self.app.collection.update_view()
             self.app.inform.emit(_("[success] Name changed from {old} to {new}").format(old=old_name, new=new_name))
 
     def on_offset_button_click(self):
@@ -338,7 +343,7 @@ class FlatCAMObj(QtCore.QObject):
         return self.shapes.visible
 
     @visible.setter
-    def visible(self, value):
+    def visible(self, value, threaded=False):
         log.debug("FlatCAMObj.visible()")
 
         def worker_task(app_obj):
@@ -350,7 +355,10 @@ class FlatCAMObj(QtCore.QObject):
             except Exception as e:
                 pass
 
-        self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
+        if threaded is False:
+            worker_task(self)
+        else:
+            self.app.worker_task.emit({'fcn': worker_task, 'params': [self]})
 
     @property
     def drawing_tolerance(self):
@@ -810,7 +818,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
 
         def follow_init(follow_obj, app):
             # Propagate options
-            follow_obj.options["cnctooldia"] = float(self.options["isotooldia"])
+            follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
             follow_obj.solid_geometry = self.follow_geometry
 
         # TODO: Do something if this is None. Offer changing name?
@@ -832,7 +840,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         :return: None
         """
 
-
         if dia is None:
             dia = float(self.options["isotooldia"])
         if passes is None:
@@ -893,7 +900,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             # TODO: This is ugly. Create way to pass data into init function.
             def iso_init(geo_obj, app_obj):
                 # Propagate options
-                geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
+                geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
                 geo_obj.solid_geometry = []
                 for i in range(passes):
                     iso_offset = (((2 * i + 1) / 2.0) * dia) - (i * overlap * dia)
@@ -950,7 +957,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
                 # TODO: This is ugly. Create way to pass data into init function.
                 def iso_init(geo_obj, app_obj):
                     # Propagate options
-                    geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
+                    geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
 
                     # if milling type is climb then the move is counter-clockwise around features
                     if milling_type == 'cl':
@@ -1638,7 +1645,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                         exc.app.log.warning("Failed to copy option.", option)
 
             for drill in exc.drills:
-                exc_tool_dia = float('%.3f' % exc.tools[drill['tool']]['C'])
+                exc_tool_dia = float('%.4f' % exc.tools[drill['tool']]['C'])
 
                 if exc_tool_dia not in custom_dict_drills:
                     custom_dict_drills[exc_tool_dia] = [drill['point']]
@@ -1646,7 +1653,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                     custom_dict_drills[exc_tool_dia].append(drill['point'])
 
             for slot in exc.slots:
-                exc_tool_dia = float('%.3f' % exc.tools[slot['tool']]['C'])
+                exc_tool_dia = float('%.4f' % exc.tools[slot['tool']]['C'])
 
                 if exc_tool_dia not in custom_dict_slots:
                     custom_dict_slots[exc_tool_dia] = [[slot['start'], slot['stop']]]
@@ -1739,7 +1746,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                 temp_tools[tool_name_temp] = spec_temp
 
                 for drill in exc_final.drills:
-                    exc_tool_dia = float('%.3f' % exc_final.tools[drill['tool']]['C'])
+                    exc_tool_dia = float('%.4f' % exc_final.tools[drill['tool']]['C'])
                     if exc_tool_dia == ordered_dia:
                         temp_drills.append(
                             {
@@ -1749,7 +1756,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                         )
 
                 for slot in exc_final.slots:
-                    slot_tool_dia = float('%.3f' % exc_final.tools[slot['tool']]['C'])
+                    slot_tool_dia = float('%.4f' % exc_final.tools[slot['tool']]['C'])
                     if slot_tool_dia == ordered_dia:
                         temp_slots.append(
                             {
@@ -1825,7 +1832,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             if self.units == 'MM':
                 dia = QtWidgets.QTableWidgetItem('%.2f' % (self.tools[tool_no]['C']))
             else:
-                dia = QtWidgets.QTableWidgetItem('%.3f' % (self.tools[tool_no]['C']))
+                dia = QtWidgets.QTableWidgetItem('%.4f' % (self.tools[tool_no]['C']))
 
             dia.setFlags(QtCore.Qt.ItemIsEnabled)
 
@@ -1843,7 +1850,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                 if self.units == 'MM':
                     t_offset = self.tool_offset[float('%.2f' % float(self.tools[tool_no]['C']))]
                 else:
-                    t_offset = self.tool_offset[float('%.3f' % float(self.tools[tool_no]['C']))]
+                    t_offset = self.tool_offset[float('%.4f' % float(self.tools[tool_no]['C']))]
             except KeyError:
                     t_offset = self.app.defaults['excellon_offset']
             tool_offset_item = QtWidgets.QTableWidgetItem('%s' % str(t_offset))
@@ -1934,10 +1941,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         self.ui.tools_table.setColumnWidth(5, 17)
 
         # horizontal_header.setStretchLastSection(True)
-
-
-
-
         # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents)
 
         # horizontal_header.setStretchLastSection(True)
@@ -2022,7 +2025,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
                 if self.units == 'MM':
                     dia = float('%.2f' % float(value['C']))
                 else:
-                    dia = float('%.3f' % float(value['C']))
+                    dia = float('%.4f' % float(value['C']))
                 self.tool_offset[dia] = t_default_offset
 
         # Show/Hide Advanced Options
@@ -2086,7 +2089,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
         if self.units == 'MM':
             dia = float('%.2f' % float(self.ui.tools_table.item(row_of_item_changed, 1).text()))
         else:
-            dia = float('%.3f' % float(self.ui.tools_table.item(row_of_item_changed, 1).text()))
+            dia = float('%.4f' % float(self.ui.tools_table.item(row_of_item_changed, 1).text()))
 
         current_table_offset_edited = None
         if self.ui.tools_table.currentItem() is not None:
@@ -2349,6 +2352,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
 
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
+            geo_obj.options["cnctooldia"] = str(tooldia)
 
             geo_obj.solid_geometry = []
 
@@ -2443,6 +2447,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
 
             geo_obj.options['Tools_in_use'] = tool_table_items
             geo_obj.options['type'] = 'Excellon Geometry'
+            geo_obj.options["cnctooldia"] = str(tooldia)
 
             geo_obj.solid_geometry = []
 
@@ -2953,6 +2958,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         if "cnctooldia" not in self.options:
             self.options["cnctooldia"] =  self.app.defaults["geometry_cnctooldia"]
+            # try:
+            #     self.options["cnctooldia"] = [
+            #         float(eval(dia)) for dia in str(self.app.defaults["geometry_cnctooldia"]).split(",")
+            #     ]
+            # except Exception as e:
+            #     log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> Geometry General -> "
+            #               "Tool dia. %s" % str(e))
+            #     return
 
         self.options["startz"] = self.app.defaults["geometry_startz"]
 
@@ -2998,9 +3011,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.old_pp_state = ''
         self.old_toolchangeg_state = ''
 
-        # store here the default data for Geometry Data
-        self.default_data = {}
-
         # Attributes to be included in serialization
         # Always append to it because it carries contents
         # from predecessors.
@@ -3009,7 +3019,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
     def build_ui(self):
 
         self.ui_disconnect()
-
         FlatCAMObj.build_ui(self)
 
         offset = 0
@@ -3147,6 +3156,10 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         self.set_tool_offset_visibility(selected_row)
         self.ui_connect()
 
+        # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename
+        # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed.
+        self.ui.name_entry.set_value(self.options['name'])
+
     def set_ui(self, ui):
         FlatCAMObj.set_ui(self, ui)
 
@@ -3221,21 +3234,33 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         for def_key in self.default_data:
             for opt_key, opt_val in self.options.items():
                 if def_key == opt_key:
-                    self.default_data[def_key] = opt_val
+                    self.default_data[def_key] = deepcopy(opt_val)
+
+        try:
+            tools_list = [
+                float(eval(dia)) for dia in self.options["cnctooldia"].split(",")
+            ]
+        except Exception as e:
+            log.error("At least one tool diameter needed. Verify in Edit -> Preferences -> Geometry General -> "
+                      "Tool dia. %s" % str(e))
+            return
 
         self.tooluid += 1
+
         if not self.tools:
-            self.tools.update({
-                self.tooluid: {
-                    'tooldia': float(self.options["cnctooldia"]),
-                    'offset': ('Path'),
-                    'offset_value': 0.0,
-                    'type': _('Rough'),
-                    'tool_type': 'C1',
-                    'data': self.default_data,
-                    'solid_geometry': self.solid_geometry
-                }
-            })
+            for toold in tools_list:
+                self.tools.update({
+                    self.tooluid: {
+                        'tooldia': float(toold),
+                        'offset': ('Path'),
+                        'offset_value': 0.0,
+                        'type': _('Rough'),
+                        'tool_type': 'C1',
+                        'data': self.default_data,
+                        'solid_geometry': self.solid_geometry
+                    }
+                })
+                self.tooluid += 1
         else:
             # if self.tools is not empty then it can safely be assumed that it comes from an opened project.
             # Because of the serialization the self.tools list on project save, the dict keys (members of self.tools
@@ -3321,10 +3346,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             return
 
     def on_offset_value_edited(self):
-        '''
-        This will save the offset_value into self.tools storage whenever the oofset value is edited
+        """
+        This will save the offset_value into self.tools storage whenever the offset value is edited
         :return:
-        '''
+        """
+
         for current_row in self.ui.geo_tools_table.selectedItems():
             # sometime the header get selected and it has row number -1
             # we don't want to do anything with the header :)
@@ -3367,7 +3393,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     # works for Entry
                     try:
                         self.ui.grid3.itemAt(i).widget().editingFinished.connect(self.gui_form_to_storage)
-                    except:
+                    except Exception as e3:
                         pass
 
         for row in range(self.ui.geo_tools_table.rowCount()):
@@ -3405,56 +3431,56 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                         isinstance(self.ui.grid3.itemAt(i).widget(), IntEntry) or \
                         isinstance(self.ui.grid3.itemAt(i).widget(), FCEntry):
                     self.ui.grid3.itemAt(i).widget().editingFinished.disconnect()
-        except:
+        except Exception as e:
             pass
 
         try:
             for row in range(self.ui.geo_tools_table.rowCount()):
                 for col in [2, 3, 4]:
                     self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.disconnect()
-        except:
+        except Exception as e:
             pass
 
         # I use lambda's because the connected functions have parameters that could be used in certain scenarios
         try:
             self.ui.addtool_btn.clicked.disconnect()
-        except:
+        except Exception as e:
             pass
 
         try:
             self.ui.copytool_btn.clicked.disconnect()
-        except:
+        except Exception as e:
             pass
 
         try:
             self.ui.deltool_btn.clicked.disconnect()
-        except:
+        except Exception as e:
             pass
 
         try:
             self.ui.geo_tools_table.currentItemChanged.disconnect()
-        except:
+        except Exception as e:
             pass
 
         try:
             self.ui.geo_tools_table.itemChanged.disconnect()
-        except:
+        except Exception as e:
             pass
 
         try:
             self.ui.tool_offset_entry.editingFinished.disconnect()
-        except:
+        except Exception as e:
             pass
 
         for row in range(self.ui.geo_tools_table.rowCount()):
             try:
                 self.ui.geo_tools_table.cellWidget(row, 6).clicked.disconnect()
-            except:
+            except Exception as e:
                 pass
 
         try:
             self.ui.plot_cb.stateChanged.disconnect()
-        except:
+        except Exception as e:
             pass
 
     def on_tool_add(self, dia=None):
@@ -3475,7 +3501,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                     tooldia = float(self.ui.addtool_entry.get_value().replace(',', '.'))
                 except ValueError:
                     change_message = True
-                    tooldia = float(self.app.defaults["geometry_cnctooldia"])
+                    tooldia = self.options["cnctooldia"][0]
 
             if tooldia is None:
                 self.build_ui()
@@ -3541,6 +3567,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
                 }
             })
 
+        self.tools[self.tooluid]['data']['name'] = self.options['name']
+
         self.ui.tool_offset_entry.hide()
         self.ui.tool_offset_lbl.hide()
 
@@ -3772,7 +3800,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
         # populate the form with the data from the tool associated with the row parameter
         try:
-            tooluid = int(self.ui.geo_tools_table.item(current_row, 5).text())
+            item = self.ui.geo_tools_table.item(current_row, 5)
+            if item is not None:
+                tooluid = int(item.text())
+            else:
+                return
         except Exception as e:
             log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e))
             return
@@ -3780,8 +3812,12 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
         # update the form with the V-Shape fields if V-Shape selected in the geo_tool_table
         # also modify the Cut Z form entry to reflect the calculated Cut Z from values got from V-Shape Fields
         try:
-            tool_type_txt = self.ui.geo_tools_table.cellWidget(current_row, 4).currentText()
-            self.ui_update_v_shape(tool_type_txt=tool_type_txt)
+            item = self.ui.geo_tools_table.cellWidget(current_row, 4)
+            if item is not None:
+                tool_type_txt = item.currentText()
+                self.ui_update_v_shape(tool_type_txt=tool_type_txt)
+            else:
+                return
         except Exception as e:
             log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e))
             return
@@ -4137,7 +4173,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
             self.ui.cncfeedrate_rapid_entry.hide()
 
     def on_generatecnc_button_click(self, *args):
-
+        log.debug("Generating CNCJob from Geometry ...")
         self.app.report_usage("geometry_on_generatecnc_button")
         self.read_form()
 
@@ -5291,6 +5327,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
         else:
             self.ui.cnc_tools_table.hide()
 
+        self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
 
         offset = 0
         tool_idx = 0
@@ -5891,13 +5928,19 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
             self.shapes.clear(update=True)
             self.annotation.clear(update=True)
 
+        if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value():
+            self.app.plotcanvas.text_collection.enabled = True
+        else:
+            self.app.plotcanvas.text_collection.enabled = False
+
     def on_annotation_change(self):
         if self.ui.annotation_cb.get_value():
             self.app.plotcanvas.text_collection.enabled = True
         else:
             self.app.plotcanvas.text_collection.enabled = False
-        kind = self.ui.cncplot_method_combo.get_value()
-        self.plot(kind=kind)
+        # kind = self.ui.cncplot_method_combo.get_value()
+        # self.plot(kind=kind)
+        self.annotation.redraw()
 
     def convert_units(self, units):
         factor = CNCjob.convert_units(self, units)

+ 4 - 4
ObjectCollection.py

@@ -263,10 +263,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
 
         # ## GUI Events
         self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
-        self.view.activated.connect(self.on_item_activated)
-        # self.view.keyPressed.connect(self.on_key)
+        # self.view.activated.connect(self.on_item_activated)
         self.view.keyPressed.connect(self.app.ui.keyPressEvent)
-        self.view.clicked.connect(self.on_mouse_down)
+        # self.view.clicked.connect(self.on_mouse_down)
         self.view.customContextMenuRequested.connect(self.on_menu_request)
 
         self.click_modifier = None
@@ -398,8 +397,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
     def setData(self, index, data, role=None):
         if index.isValid():
             obj = index.internalPointer().obj
+
             if obj:
-                old_name = obj.options['name']
+                old_name = deepcopy(obj.options['name'])
                 new_name = str(data)
                 if old_name != new_name and new_name != '':
                     # rename the object

+ 43 - 0
README.md

@@ -9,6 +9,49 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+22.06.2019
+
+- some GUI layout optimizations in Edit -> Preferences
+- added the possibility for multiple tool diameters in the Edit -> Preferences -> Geometry -> Geometry General -> Tool dia separated by comma
+- fixed scaling for the multiple tool diameters in Edit -> Preferences -> Geometry -> Geometry General -> Tool dia, for NCC tools more than 2 and for Solderpaste nozzles more than 2
+- fixed bug in CNCJob where the CNC Tools table will show always only 2 decimals for Tool diameters regardless of the current measuring units
+- made the tools diameters decimals in case of INCH FlatCAM units to be 4 instead of 3
+- fixed bug in updating Grid values whenever toggling the FlatCAM units and the X, Y Grid values are linked, bugs which caused the Y value to be scaled incorrectly
+- set the decimals for Grid values to be set to 6 if the units of FlatCAM is INCH and to set to 4 if FlatCAM units are METRIC
+- updated translations
+- updated the Russian translation from 51% complete to 69% complete using the Yandex translation engine
+- fixed recently introduced bug in milling drills/slots functions
+- moved Substract Tool from Menu -> Edit -> Conversions to Menu -> Tool
+- fixed bug in Gerber isolation (Geometry expects now a value in string format and not float)
+- fixed bug in Paint tool: now it is possible to paint geometry generated by External Isolation (or Internal isolation)
+- fixed bug in editing a multigeo Geometry object if previously a tool was deleted
+- optimized the toggle of annotations; now there is no need to replot the entire CNCJob object too on toggling of the annotations
+- on toggling off the plot visibility the annotations are turned off too
+- updated translations; Russian translation at 76% (using Yandex translator engine - needs verification by a native speaker of Russian)
+- RELEASE 8.919
+
+20.06.2019
+
+- fixed Scale and Buffer Tool in Gerber Editor
+- fixed Editor Transform Tool in Gerber Editor
+- added a message in the status bar when copying coordinates to clipboard with SHIFT + LMB click combo
+- languages update
+
+19.06.2019
+
+- milling an Excellon file (holes and/or slots) will now transfer the chosen milling bit diameter to the resulting Geometry object
+
+17.06.2019
+
+- fixed bug where for Geometry objects after a successful object rename done in the Object collection view (Project tab), deselect the object and reselect it and then in the Selected tab the name is not the new one but the old one
+- for Geometry objects, adding a new tool to the Tools table after a successful rename will now store the new name in the tool data
+
+15.06.2019
+
+- fixed bug in Gerber parser that made the Gerber files generated by Altium Designer 18 not to be loaded
+- fixed bug in Gerber editor - on multiple edits on the same object, the aperture size and dims were continuously multiplied due of the file units not being updated
+- restored the FlatCAMObj.visible() to a non-threaded default
+
 11.06.2019
 
 - fixed the Edit -> Conversion -> Join ... functions (merge() functions)

+ 11 - 8
camlib.py

@@ -112,9 +112,9 @@ class Geometry(object):
 
         self.geo_steps_per_circle = geo_steps_per_circle
 
-        if geo_steps_per_circle is None:
-            geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"])
-        self.geo_steps_per_circle = geo_steps_per_circle
+        # if geo_steps_per_circle is None:
+        #     geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"])
+        # self.geo_steps_per_circle = geo_steps_per_circle
 
     def make_index(self):
         self.flatten()
@@ -1849,6 +1849,8 @@ class ApertureMacro:
 
 class Gerber (Geometry):
     """
+    Here it is done all the Gerber parsing.
+
     **ATTRIBUTES**
 
     * ``apertures`` (dict): The keys are names/identifiers of each aperture.
@@ -2452,7 +2454,6 @@ class Gerber (Geometry):
                         # --- Buffered ---
                         try:
                             log.debug("Bare op-code %d." % current_operation_code)
-
                             geo_dict = dict()
                             flash = self.create_flash_geometry(
                                 Point(current_x, current_y), self.apertures[current_aperture],
@@ -2467,7 +2468,7 @@ class Gerber (Geometry):
                                 else:
                                     geo_dict['solid'] = flash
 
-                                if last_path_aperture not in self.apertures:
+                                if current_aperture not in self.apertures:
                                     self.apertures[current_aperture] = dict()
                                 if 'geometry' not in self.apertures[current_aperture]:
                                     self.apertures[current_aperture]['geometry'] = []
@@ -3598,6 +3599,8 @@ class Gerber (Geometry):
 
 class Excellon(Geometry):
     """
+    Here it is done all the Excellon parsing.
+
     *ATTRIBUTES*
 
     * ``tools`` (dict): The key is the tool name and the value is
@@ -5350,7 +5353,7 @@ class CNCjob(Geometry):
                             if self.units == 'MM':
                                 current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
                             else:
-                                current_tooldia = float('%.3f' % float(exobj.tools[tool]["C"]))
+                                current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
 
                             # TODO apply offset only when using the GUI, for TclCommand this will create an error
                             # because the values for Z offset are created in build_ui()
@@ -5448,7 +5451,7 @@ class CNCjob(Geometry):
                             if self.units == 'MM':
                                 current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
                             else:
-                                current_tooldia = float('%.3f' % float(exobj.tools[tool]["C"]))
+                                current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
 
                             # TODO apply offset only when using the GUI, for TclCommand this will create an error
                             # because the values for Z offset are created in build_ui()
@@ -5504,7 +5507,7 @@ class CNCjob(Geometry):
                         if self.units == 'MM':
                             current_tooldia = float('%.2f' % float(exobj.tools[tool]["C"]))
                         else:
-                            current_tooldia = float('%.3f' % float(exobj.tools[tool]["C"]))
+                            current_tooldia = float('%.4f' % float(exobj.tools[tool]["C"]))
 
                         # TODO apply offset only when using the GUI, for TclCommand this will create an error
                         # because the values for Z offset are created in build_ui()

+ 3 - 3
flatcamEditors/FlatCAMExcEditor.py

@@ -1209,7 +1209,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         for drill in self.exc_obj.drills:
             if drill['tool'] in self.exc_obj.tools:
                 if self.units == 'IN':
-                    tool_dia = float('%.3f' % self.exc_obj.tools[drill['tool']]['C'])
+                    tool_dia = float('%.4f' % self.exc_obj.tools[drill['tool']]['C'])
                 else:
                     tool_dia = float('%.2f' % self.exc_obj.tools[drill['tool']]['C'])
 
@@ -1238,7 +1238,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             # but use the real order found in the exc_obj.tools
             for k, v in self.exc_obj.tools.items():
                 if self.units == 'IN':
-                    tool_dia = float('%.3f' % v['C'])
+                    tool_dia = float('%.4f' % v['C'])
                 else:
                     tool_dia = float('%.2f' % v['C'])
                 self.tool2tooldia[int(k)] = tool_dia
@@ -1324,7 +1324,7 @@ class FlatCAMExcEditor(QtCore.QObject):
             if self.units == 'MM':
                 dia = QtWidgets.QTableWidgetItem('%.2f' % self.olddia_newdia[tool_no])
             else:
-                dia = QtWidgets.QTableWidgetItem('%.3f' % self.olddia_newdia[tool_no])
+                dia = QtWidgets.QTableWidgetItem('%.4f' % self.olddia_newdia[tool_no])
 
             dia.setFlags(QtCore.Qt.ItemIsEnabled)
 

+ 5 - 2
flatcamEditors/FlatCAMGeoEditor.py

@@ -3059,8 +3059,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
                 val = float(self.app.ui.grid_gap_x_entry.get_value())
             except ValueError:
                 return
+
+            units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
+            dec = 6 if units == 'IN' else 4
             if self.app.ui.grid_gap_link_cb.isChecked():
-                self.app.ui.grid_gap_y_entry.set_value(val)
+                self.app.ui.grid_gap_y_entry.set_value(val, decimals=dec)
 
         self.app.ui.grid_gap_x_entry.setValidator(QtGui.QDoubleValidator())
         self.app.ui.grid_gap_x_entry.textChanged.connect(
@@ -3999,7 +4002,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
 
     def update_options(self, obj):
         if self.paint_tooldia:
-            obj.options['cnctooldia'] = self.paint_tooldia
+            obj.options['cnctooldia'] = deepcopy(str(self.paint_tooldia))
             self.paint_tooldia = None
             return True
         else:

+ 83 - 66
flatcamEditors/FlatCAMGrbEditor.py

@@ -3445,7 +3445,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         file_units = self.gerber_obj.gerber_units if self.gerber_obj.gerber_units else 'IN'
         app_units = self.app.defaults['units']
-
         self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1
 
         # Hide original geometry
@@ -3478,6 +3477,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
                     conv_apertures[apid][key] = self.gerber_obj.apertures[apid][key]
 
         self.gerber_obj.apertures = conv_apertures
+        self.gerber_obj.gerber_units = app_units
 
         # ############################################################# ##
         # APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
@@ -3575,7 +3575,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
 
         :return: None
         """
-
         new_grb_name = self.edited_obj_name
 
         # if the 'delayed plot' malfunctioned stop the QTimer
@@ -3710,6 +3709,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
             grb_obj.source_file = []
             grb_obj.multigeo = False
             grb_obj.follow = False
+            grb_obj.gerber_units = app_obj.defaults['units']
 
             try:
                 grb_obj.create_geometry()
@@ -3890,7 +3890,17 @@ class FlatCAMGrbEditor(QtCore.QObject):
                                                    "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0, 0))
 
             # Selection with left mouse button
-            if self.active_tool is not None and event.button is 1:
+            if self.active_tool is not None:
+                modifiers = QtWidgets.QApplication.keyboardModifiers()
+
+                # If the SHIFT key is pressed when LMB is clicked then the coordinates are copied to clipboard
+                if modifiers == QtCore.Qt.ShiftModifier:
+                    self.app.clipboard.setText(
+                        self.app.defaults["global_point_clipboard_format"] % (self.pos[0], self.pos[1])
+                    )
+                    self.app.inform.emit(_("[success] Coordinates copied to clipboard."))
+                    return
+
                 # Dispatch event to active_tool
                 self.active_tool.click(self.app.geo_editor.snap(self.pos[0], self.pos[1]))
 
@@ -4406,19 +4416,13 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 if geom_el in selection:
                     geometric_data = geom_el.geo
                     buffered_geom_el = dict()
-                    if 'solid' in geom_el:
-                        buffered_geom_el['solid'] = DrawToolShape(
-                            geometric_data['solid'].buffer(buff_value, join_style=join_style)
-                        )
-                    if 'follow' in geom_el:
-                        buffered_geom_el['follow'] = DrawToolShape(
-                            geometric_data['follow'].buffer(buff_value, join_style=join_style)
-                        )
-                    if 'clear' in geom_el:
-                        buffered_geom_el['clear'] = DrawToolShape(
-                            geometric_data['clear'].buffer(buff_value, join_style=join_style)
-                        )
-                    return buffered_geom_el
+                    if 'solid' in geometric_data:
+                        buffered_geom_el['solid'] = geometric_data['solid'].buffer(buff_value, join_style=join_style)
+                    if 'follow' in geometric_data:
+                        buffered_geom_el['follow'] = geometric_data['follow'].buffer(buff_value, join_style=join_style)
+                    if 'clear' in geometric_data:
+                        buffered_geom_el['clear'] = geometric_data['clear'].buffer(buff_value, join_style=join_style)
+                    return DrawToolShape(buffered_geom_el)
                 else:
                     return geom_el
 
@@ -4435,9 +4439,10 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 temp_storage = deepcopy(buffer_recursion(self.storage_dict[apid]['geometry'], self.selected))
                 self.storage_dict[apid]['geometry'] = []
                 self.storage_dict[apid]['geometry'] = temp_storage
-
             except Exception as e:
-                log.debug("FlatCAMGrbEditor.buffer() --> %s" % str(e))
+                log.debug("FlatCAMGrbEditor.buffer() --> %s\n%s" % str(e))
+                self.app.inform.emit(_("[ERROR_NOTCL] Failed.\n%s") % str(traceback.print_exc()))
+                return
         self.plot_all()
         self.app.inform.emit(_("[success] Done. Buffer Tool completed."))
 
@@ -4467,17 +4472,20 @@ class FlatCAMGrbEditor(QtCore.QObject):
                 if geom_el in selection:
                     geometric_data = geom_el.geo
                     scaled_geom_el = dict()
-                    if 'solid' in geom_el:
-                        scaled_geom_el['solid'] = DrawToolShape(
-                            affinity.scale(geometric_data['solid'], scale_factor, scale_factor, origin='center'))
-                    if 'follow' in geom_el:
-                        scaled_geom_el['follow'] = DrawToolShape(
-                            affinity.scale(geometric_data['follow'], scale_factor, scale_factor, origin='center'))
-                    if 'clear' in geom_el:
-                        scaled_geom_el['clear'] = DrawToolShape(
-                            affinity.scale(geometric_data['clear'], scale_factor, scale_factor, origin='center'))
-
-                    return scaled_geom_el
+                    if 'solid' in geometric_data:
+                        scaled_geom_el['solid'] = affinity.scale(
+                            geometric_data['solid'], scale_factor, scale_factor, origin='center'
+                        )
+                    if 'follow' in geometric_data:
+                        scaled_geom_el['follow'] = affinity.scale(
+                            geometric_data['follow'], scale_factor, scale_factor, origin='center'
+                        )
+                    if 'clear' in geometric_data:
+                        scaled_geom_el['clear'] = affinity.scale(
+                            geometric_data['clear'], scale_factor, scale_factor, origin='center'
+                        )
+
+                    return DrawToolShape(scaled_geom_el)
                 else:
                     return geom_el
 
@@ -5229,9 +5237,10 @@ class TransformEditorTool(FlatCAMTool):
             try:
                 # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
                 # bounding box
-                for el in elem_list:
+                for el_shape in elem_list:
+                    el = el_shape.geo
                     if 'solid' in el:
-                        xmin, ymin, xmax, ymax = el['solid'].bounds()
+                        xmin, ymin, xmax, ymax = el['solid'].bounds
                         xminlist.append(xmin)
                         yminlist.append(ymin)
                         xmaxlist.append(xmax)
@@ -5247,13 +5256,14 @@ class TransformEditorTool(FlatCAMTool):
                 px = 0.5 * (xminimal + xmaximal)
                 py = 0.5 * (yminimal + ymaximal)
 
-                for sel_el in elem_list:
+                for sel_el_shape in elem_list:
+                    sel_el = sel_el_shape.geo
                     if 'solid' in sel_el:
-                        sel_el['solid'].rotate(-num, point=(px, py))
+                        sel_el['solid'] = affinity.rotate(sel_el['solid'], angle=-num, origin=(px, py))
                     if 'follow' in sel_el:
-                        sel_el['follow'].rotate(-num, point=(px, py))
+                        sel_el['follow'] = affinity.rotate(sel_el['follow'], angle=-num, origin=(px, py))
                     if 'clear' in sel_el:
-                        sel_el['clear'].rotate(-num, point=(px, py))
+                        sel_el['clear'] = affinity.rotate(sel_el['clear'], angle=-num, origin=(px, py))
                 self.draw_app.plot_all()
 
                 self.app.inform.emit(_("[success] Done. Rotate completed."))
@@ -5287,9 +5297,10 @@ class TransformEditorTool(FlatCAMTool):
                 else:
                     # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
                     # bounding box
-                    for el in elem_list:
+                    for el_shape in elem_list:
+                        el = el_shape.geo
                         if 'solid' in el:
-                            xmin, ymin, xmax, ymax = el['solid'].bounds()
+                            xmin, ymin, xmax, ymax = el['solid'].bounds
                             xminlist.append(xmin)
                             yminlist.append(ymin)
                             xmaxlist.append(xmax)
@@ -5307,22 +5318,23 @@ class TransformEditorTool(FlatCAMTool):
                 self.app.progress.emit(20)
 
                 # execute mirroring
-                for sel_el in elem_list:
+                for sel_el_shape in elem_list:
+                    sel_el = sel_el_shape.geo
                     if axis is 'X':
                         if 'solid' in sel_el:
-                            sel_el['solid'].mirror('X', (px, py))
+                            sel_el['solid'] = affinity.scale(sel_el['solid'], xfact=1, yfact=-1, origin=(px, py))
                         if 'follow' in sel_el:
-                            sel_el['follow'].mirror('X', (px, py))
+                            sel_el['follow'] = affinity.scale(sel_el['follow'], xfact=1, yfact=-1, origin=(px, py))
                         if 'clear' in sel_el:
-                            sel_el['clear'].mirror('X', (px, py))
+                            sel_el['clear'] = affinity.scale(sel_el['clear'], xfact=1, yfact=-1, origin=(px, py))
                         self.app.inform.emit(_('[success] Flip on the Y axis done ...'))
                     elif axis is 'Y':
                         if 'solid' in sel_el:
-                            sel_el['solid'].mirror('Y', (px, py))
+                            sel_el['solid'] = affinity.scale(sel_el['solid'], xfact=-1, yfact=1, origin=(px, py))
                         if 'follow' in sel_el:
-                            sel_el['follow'].mirror('Y', (px, py))
+                            sel_el['follow'] = affinity.scale(sel_el['follow'], xfact=-1, yfact=1, origin=(px, py))
                         if 'clear' in sel_el:
-                            sel_el['clear'].mirror('Y', (px, py))
+                            sel_el['clear'] = affinity.scale(sel_el['clear'], xfact=-1, yfact=1, origin=(px, py))
                         self.app.inform.emit(_('[success] Flip on the X axis done ...'))
                 self.draw_app.plot_all()
                 self.app.progress.emit(100)
@@ -5350,9 +5362,10 @@ class TransformEditorTool(FlatCAMTool):
                 try:
                     # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
                     # bounding box
-                    for el in elem_list:
+                    for el_shape in elem_list:
+                        el = el_shape.geo
                         if 'solid' in el:
-                            xmin, ymin, xmax, ymax = el['solid'].bounds()
+                            xmin, ymin, xmax, ymax = el['solid'].bounds
                             xminlist.append(xmin)
                             yminlist.append(ymin)
 
@@ -5362,21 +5375,22 @@ class TransformEditorTool(FlatCAMTool):
 
                     self.app.progress.emit(20)
 
-                    for sel_el in elem_list:
+                    for sel_el_shape in elem_list:
+                        sel_el = sel_el_shape.geo
                         if axis is 'X':
                             if 'solid' in sel_el:
-                                sel_el['solid'].skew(num, 0, point=(xminimal, yminimal))
+                                sel_el['solid'] = affinity.skew(sel_el['solid'], num, 0, origin=(xminimal, yminimal))
                             if 'follow' in sel_el:
-                                sel_el['follow'].skew(num, 0, point=(xminimal, yminimal))
+                                sel_el['follow'] = affinity.skew(sel_el['follow'], num, 0, origin=(xminimal, yminimal))
                             if 'clear' in sel_el:
-                                sel_el['clear'].skew(num, 0, point=(xminimal, yminimal))
+                                sel_el['clear'] = affinity.skew(sel_el['clear'], num, 0, origin=(xminimal, yminimal))
                         elif axis is 'Y':
                             if 'solid' in sel_el:
-                                sel_el['solid'].skew(0, num, point=(xminimal, yminimal))
+                                sel_el['solid'] = affinity.skew(sel_el['solid'], 0, num, origin=(xminimal, yminimal))
                             if 'follow' in sel_el:
-                                sel_el['follow'].skew(0, num, point=(xminimal, yminimal))
+                                sel_el['follow'] = affinity.skew(sel_el['follow'], 0, num, origin=(xminimal, yminimal))
                             if 'clear' in sel_el:
-                                sel_el['clear'].skew(0, num, point=(xminimal, yminimal))
+                                sel_el['clear'] = affinity.skew(sel_el['clear'], 0, num, origin=(xminimal, yminimal))
                     self.draw_app.plot_all()
 
                     self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis))
@@ -5409,9 +5423,10 @@ class TransformEditorTool(FlatCAMTool):
                 try:
                     # first get a bounding box to fit all; we use only the 'solids' as those should provide the biggest
                     # bounding box
-                    for el in elem_list:
+                    for el_shape in elem_list:
+                        el = el_shape.geo
                         if 'solid' in el:
-                            xmin, ymin, xmax, ymax = el['solid'].bounds()
+                            xmin, ymin, xmax, ymax = el['solid'].bounds
                             xminlist.append(xmin)
                             yminlist.append(ymin)
                             xmaxlist.append(xmax)
@@ -5432,13 +5447,14 @@ class TransformEditorTool(FlatCAMTool):
                         px = 0
                         py = 0
 
-                    for sel_el in elem_list:
+                    for sel_el_shape in elem_list:
+                        sel_el = sel_el_shape.geo
                         if 'solid' in sel_el:
-                            sel_el['solid'].scale(xfactor, yfactor, point=(px, py))
+                            sel_el['solid'] = affinity.scale(sel_el['solid'], xfactor, yfactor, origin=(px, py))
                         if 'follow' in sel_el:
-                            sel_el['follow'].scale(xfactor, yfactor, point=(px, py))
+                            sel_el['follow'] = affinity.scale(sel_el['follow'], xfactor, yfactor, origin=(px, py))
                         if 'clear' in sel_el:
-                            sel_el['clear'].scale(xfactor, yfactor, point=(px, py))
+                            sel_el['clear'] = affinity.scale(sel_el['clear'], xfactor, yfactor, origin=(px, py))
                     self.draw_app.plot_all()
 
                     self.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis))
@@ -5464,21 +5480,22 @@ class TransformEditorTool(FlatCAMTool):
                 try:
                     self.app.progress.emit(20)
 
-                    for sel_el in elem_list:
+                    for sel_el_shape in elem_list:
+                        sel_el = sel_el_shape.geo
                         if axis is 'X':
                             if 'solid' in sel_el:
-                                sel_el['solid'].offset((num, 0))
+                                sel_el['solid'] = affinity.translate(sel_el['solid'], num, 0)
                             if 'follow' in sel_el:
-                                sel_el['follow'].offset((num, 0))
+                                sel_el['follow'] = affinity.translate(sel_el['follow'], num, 0)
                             if 'clear' in sel_el:
-                                sel_el['clear'].offset((num, 0))
+                                sel_el['clear'] = affinity.translate(sel_el['clear'], num, 0)
                         elif axis is 'Y':
                             if 'solid' in sel_el:
-                                sel_el['solid'].offset((0, num))
+                                sel_el['solid'] = affinity.translate(sel_el['solid'], 0, num)
                             if 'follow' in sel_el:
-                                sel_el['follow'].offset((0, num))
+                                sel_el['follow'] = affinity.translate(sel_el['follow'], 0, num)
                             if 'clear' in sel_el:
-                                sel_el['clear'].offset((0, num))
+                                sel_el['clear'] = affinity.translate(sel_el['clear'], 0, num)
                         self.draw_app.plot_all()
 
                     self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))

+ 26 - 47
flatcamGUI/FlatCAMGUI.py

@@ -4096,7 +4096,7 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
         self.combine_passes_cb.setToolTip(
             _("Combine all passes into one object")
         )
-        grid0.addWidget(self.combine_passes_cb, 4, 0)
+        grid0.addWidget(self.combine_passes_cb, 4, 0, 1, 2)
 
         # ## Clear non-copper regions
         self.clearcopper_label = QtWidgets.QLabel(_("<b>Clear non-copper:</b>"))
@@ -4468,8 +4468,8 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
         hlay2.addWidget(self.excellon_format_lower_mm_entry, QtCore.Qt.AlignLeft)
         hlay2.addStretch()
 
-        hlay3 = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay3)
+        grid2 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid2)
 
         self.excellon_zeros_label = QtWidgets.QLabel(_('Default <b>Zeros</b>:'))
         self.excellon_zeros_label.setAlignment(QtCore.Qt.AlignLeft)
@@ -4480,7 +4480,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "If TZ is checked then Trailing Zeros are kept\n"
               "and Leading Zeros are removed.")
         )
-        hlay3.addWidget(self.excellon_zeros_label)
+        grid2.addWidget(self.excellon_zeros_label, 0, 0)
 
         self.excellon_zeros_radio = RadioSet([{'label': 'LZ', 'value': 'L'},
                                               {'label': 'TZ', 'value': 'T'}])
@@ -4493,11 +4493,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "If TZ is checked then Trailing Zeros are kept\n"
               "and Leading Zeros are removed.")
         )
-        hlay3.addStretch()
-        hlay3.addWidget(self.excellon_zeros_radio, QtCore.Qt.AlignRight)
-
-        hlay4 = QtWidgets.QHBoxLayout()
-        self.layout.addLayout(hlay4)
+        grid2.addWidget(self.excellon_zeros_radio, 0, 1)
 
         self.excellon_units_label = QtWidgets.QLabel(_('Default <b>Units</b>:'))
         self.excellon_units_label.setAlignment(QtCore.Qt.AlignLeft)
@@ -4508,7 +4504,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "Some Excellon files don't have an header\n"
               "therefore this parameter will be used.")
         )
-        hlay4.addWidget(self.excellon_units_label)
+        grid2.addWidget(self.excellon_units_label, 1, 0)
 
         self.excellon_units_radio = RadioSet([{'label': 'INCH', 'value': 'INCH'},
                                               {'label': 'MM', 'value': 'METRIC'}])
@@ -4517,27 +4513,14 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "Some Excellon files don't have an header\n"
               "therefore this parameter will be used.")
         )
-        hlay4.addStretch()
-        hlay4.addWidget(self.excellon_units_radio, QtCore.Qt.AlignRight)
+        grid2.addWidget(self.excellon_units_radio, 1, 1)
 
-        hlay5 = QtWidgets.QVBoxLayout()
-        self.layout.addLayout(hlay5)
-
-        self.empty_label = QtWidgets.QLabel("")
-        hlay5.addWidget(self.empty_label)
-
-        hlay6 = QtWidgets.QVBoxLayout()
-        self.layout.addLayout(hlay6)
+        grid2.addWidget(QtWidgets.QLabel(""), 2, 0)
 
         self.excellon_general_label = QtWidgets.QLabel(_("<b>Excellon Optimization:</b>"))
-        hlay6.addWidget(self.excellon_general_label)
-
-        # Create a form layout for the Excellon general settings
-        form_box_excellon = QtWidgets.QFormLayout()
-        hlay6.addLayout(form_box_excellon)
+        grid2.addWidget(self.excellon_general_label, 3, 0, 1, 2)
 
         self.excellon_optimization_label = QtWidgets.QLabel(_('Algorithm:   '))
-        self.excellon_optimization_label.setAlignment(QtCore.Qt.AlignLeft)
         self.excellon_optimization_label.setToolTip(
             _("This sets the optimization type for the Excellon drill path.\n"
               "If MH is checked then Google OR-Tools algorithm with MetaHeuristic\n"
@@ -4548,6 +4531,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "If DISABLED, then FlatCAM works in 32bit mode and it uses \n"
               "Travelling Salesman algorithm for path optimization.")
         )
+        grid2.addWidget(self.excellon_optimization_label, 4, 0)
 
         self.excellon_optimization_radio = RadioSet([{'label': 'MH', 'value': 'M'},
                                      {'label': 'Basic', 'value': 'B'}])
@@ -4561,8 +4545,7 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "If DISABLED, then FlatCAM works in 32bit mode and it uses \n"
               "Travelling Salesman algorithm for path optimization.")
         )
-
-        form_box_excellon.addRow(self.excellon_optimization_label, self.excellon_optimization_radio)
+        grid2.addWidget(self.excellon_optimization_radio, 4, 1)
 
         self.optimization_time_label = QtWidgets.QLabel(_('Optimization Time:   '))
         self.optimization_time_label.setAlignment(QtCore.Qt.AlignLeft)
@@ -4573,10 +4556,11 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI):
               "In seconds.")
 
         )
+        grid2.addWidget(self.optimization_time_label, 5, 0)
 
         self.optimization_time_entry = IntEntry()
         self.optimization_time_entry.setValidator(QtGui.QIntValidator(0, 999))
-        form_box_excellon.addRow(self.optimization_time_label, self.optimization_time_entry)
+        grid2.addWidget(self.optimization_time_entry, 5, 1)
 
         current_platform = platform.architecture()[0]
         if current_platform == '64bit':
@@ -4744,25 +4728,23 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
         self.mill_hole_label.setToolTip(
             _("Create Geometry for milling holes.")
         )
-        self.layout.addWidget(self.mill_hole_label)
+        grid2.addWidget(excellon_gcode_type_label, 11, 0, 1, 2)
 
-        grid3 = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid3)
         tdlabel = QtWidgets.QLabel(_('Drill Tool dia:'))
         tdlabel.setToolTip(
             _("Diameter of the cutting tool.")
         )
-        grid3.addWidget(tdlabel, 0, 0)
+        grid2.addWidget(tdlabel, 12, 0)
         self.tooldia_entry = LengthEntry()
-        grid3.addWidget(self.tooldia_entry, 0, 1)
+        grid2.addWidget(self.tooldia_entry, 12, 1)
         stdlabel = QtWidgets.QLabel(_('Slot Tool dia:'))
         stdlabel.setToolTip(
             _("Diameter of the cutting tool\n"
               "when milling slots.")
         )
-        grid3.addWidget(stdlabel, 1, 0)
+        grid2.addWidget(stdlabel, 13, 0)
         self.slot_tooldia_entry = LengthEntry()
-        grid3.addWidget(self.slot_tooldia_entry, 1, 1)
+        grid2.addWidget(self.slot_tooldia_entry, 13, 1)
 
         grid4 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid4)
@@ -5162,6 +5144,7 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
 
         grid0 = QtWidgets.QGridLayout()
         self.layout.addLayout(grid0)
+
         # Number of circle steps for circular aperture linear approximation
         self.circle_steps_label = QtWidgets.QLabel(_("Circle Steps:"))
         self.circle_steps_label.setToolTip(
@@ -5173,21 +5156,17 @@ class GeometryGenPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.circle_steps_entry, 1, 1)
 
         # Tools
-        self.tools_label = QtWidgets.QLabel(_("<b>Tools</b>"))
-        self.layout.addWidget(self.tools_label)
-
-        grid0_b = QtWidgets.QGridLayout()
-        self.layout.addLayout(grid0_b)
+        self.tools_label = QtWidgets.QLabel(_("<b>Tools:</b>"))
+        grid0.addWidget(self.tools_label, 2, 0, 1, 2)
 
         # Tooldia
-        tdlabel = QtWidgets.QLabel(_('Tool dia:                   '))
+        tdlabel = QtWidgets.QLabel(_('Tool dia:'))
         tdlabel.setToolTip(
-            _("The diameter of the cutting\n"
-              "tool..")
+            _("Diameters of the cutting tools, separated by ','")
         )
-        grid0_b.addWidget(tdlabel, 0, 0)
-        self.cnctooldia_entry = LengthEntry()
-        grid0_b.addWidget(self.cnctooldia_entry, 0, 1)
+        grid0.addWidget(tdlabel, 3, 0)
+        self.cnctooldia_entry = FCEntry()
+        grid0.addWidget(self.cnctooldia_entry, 3, 1)
 
         self.layout.addStretch()
 

+ 8 - 7
flatcamGUI/GUIElements.py

@@ -390,12 +390,13 @@ class FCEntry2(FCEntry):
     def on_edit_finished(self):
         self.clearFocus()
 
-    def set_value(self, val):
+    def set_value(self, val, decimals=4):
         try:
             fval = float(val)
         except ValueError:
             return
-        self.setText('%.4f' % fval)
+
+        self.setText('%.*f' % (decimals, fval))
 
 
 class EvalEntry(QtWidgets.QLineEdit):
@@ -1059,7 +1060,6 @@ class FCDetachableTab(QtWidgets.QTabWidget):
             # Re-attach the tab at the given index
             self.attachTab(contentWidget, name, icon, index)
 
-
         # If the drop did not occur on an existing tab, determine if the drop
         # occurred in the tab bar area (the area to the side of the QTabBar)
         else:
@@ -1227,14 +1227,17 @@ class FCDetachableTab(QtWidgets.QTabWidget):
             :return:
             """
             # Determine if the current movement is detected as a drag
-            if not self.dragStartPos.isNull() and ((event.pos() - self.dragStartPos).manhattanLength() < QtWidgets.QApplication.startDragDistance()):
+            if not self.dragStartPos.isNull() and \
+                    ((event.pos() - self.dragStartPos).manhattanLength() < QtWidgets.QApplication.startDragDistance()):
                 self.dragInitiated = True
 
             # If the current movement is a drag initiated by the left button
             if (((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated):
 
                 # Stop the move event
-                finishMoveEvent = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier)
+                finishMoveEvent = QtGui.QMouseEvent(
+                    QtCore.QEvent.MouseMove, event.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier
+                )
                 QtWidgets.QTabBar.mouseMoveEvent(self, finishMoveEvent)
 
                 # Convert the move event into a drag
@@ -1261,13 +1264,11 @@ class FCDetachableTab(QtWidgets.QTabWidget):
                 # Initiate the drag
                 dropAction = drag.exec_(QtCore.Qt.MoveAction | QtCore.Qt.CopyAction)
 
-
                 # For Linux:  Here, drag.exec_() will not return MoveAction on Linux.  So it
                 #             must be set manually
                 if self.dragDropedPos.x() != 0 and self.dragDropedPos.y() != 0:
                     dropAction = QtCore.Qt.MoveAction
 
-
                 # If the drag completed outside of the tab bar, detach the tab and move
                 # the content to the current cursor position
                 if dropAction == QtCore.Qt.IgnoreAction:

+ 2 - 2
flatcamParsers/ParseDXF.py

@@ -91,7 +91,7 @@ def dxfarc2shapely(arc, n_points=100):
     #     angle += step_angle
     #
     #
-    # log.debug("X = %.3f, Y = %.3f, Radius = %.3f, start_angle = %.1f, stop_angle = %.1f, step_angle = %.3f, dir=%s" %
+    # log.debug("X = %.4f, Y = %.4f, Radius = %.4f, start_angle = %.1f, stop_angle = %.1f, step_angle = %.4f, dir=%s" %
     #           (center_x, center_y, radius, start_angle, end_angle, step_angle, dir))
     #
     # geo = LineString(point_list)
@@ -142,7 +142,7 @@ def dxfarc2shapely(arc, n_points=100):
             y = center_y + radius * math.sin(math.radians(- end_angle))
         point_list.append((x, y))
 
-    # log.debug("X = %.3f, Y = %.3f, Radius = %.3f, start_angle = %.1f, stop_angle = %.1f, step_angle = %.3f" %
+    # log.debug("X = %.4f, Y = %.4f, Radius = %.4f, start_angle = %.1f, stop_angle = %.1f, step_angle = %.4f" %
     #           (center_x, center_y, radius, start_angle, end_angle, step_angle))
 
     geo = LineString(point_list)

+ 3 - 3
flatcamTools/ToolNonCopperClear.py

@@ -404,7 +404,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if self.units == 'MM':
                         dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
                     else:
-                        dia = QtWidgets.QTableWidgetItem('%.3f' % tooluid_value['tooldia'])
+                        dia = QtWidgets.QTableWidgetItem('%.4f' % tooluid_value['tooldia'])
 
                     dia.setFlags(QtCore.Qt.ItemIsEnabled)
 
@@ -787,7 +787,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         else:
                             log.debug("There are no geometries in the cleared polygon.")
 
-            geo_obj.options["cnctooldia"] = tool
+            geo_obj.options["cnctooldia"] = str(tool)
             geo_obj.multigeo = True
 
         def job_thread(app_obj):
@@ -929,7 +929,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             log.debug("There are no geometries in the cleared polygon.")
 
             geo_obj.multigeo = True
-            geo_obj.options["cnctooldia"] = tool
+            geo_obj.options["cnctooldia"] = str(tool)
 
             # check to see if geo_obj.tools is empty
             # it will be updated only if there is a solid_geometry for tools

+ 12 - 8
flatcamTools/ToolPaint.py

@@ -468,7 +468,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                     if self.units == 'MM':
                         dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
                     else:
-                        dia = QtWidgets.QTableWidgetItem('%.3f' % tooluid_value['tooldia'])
+                        dia = QtWidgets.QTableWidgetItem('%.4f' % tooluid_value['tooldia'])
 
                     dia.setFlags(QtCore.Qt.ItemIsEnabled)
 
@@ -920,7 +920,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                 log.debug("Could not Paint the polygons. %s" % str(e))
                 self.app.inform.emit(
                     _("[ERROR] Could not do Paint. Try a different combination of parameters. "
-                    "Or a different strategy of paint\n%s") % str(e))
+                      "Or a different strategy of paint\n%s") % str(e))
                 return
 
             if cp is not None:
@@ -930,7 +930,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                 else:
                     geo_obj.solid_geometry = list(cp.get_objects())
 
-            geo_obj.options["cnctooldia"] = tooldia
+            geo_obj.options["cnctooldia"] = str(tooldia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = False
             geo_obj.multitool = True
@@ -1043,7 +1043,11 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             # ## Not iterable, do the actual indexing and add.
             except TypeError:
-                self.flat_geometry.append(geometry)
+                if isinstance(geometry, LinearRing):
+                    g = Polygon(geometry)
+                    self.flat_geometry.append(g)
+                else:
+                    self.flat_geometry.append(geometry)
 
             return self.flat_geometry
 
@@ -1127,7 +1131,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                 self.paint_tools[current_uid]['data']['name'] = name
                 total_geometry[:] = []
 
-            geo_obj.options["cnctooldia"] = tool_dia
+            geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = True
             geo_obj.multitool = True
@@ -1141,8 +1145,8 @@ class ToolPaint(FlatCAMTool, Gerber):
                     has_solid_geo += 1
             if has_solid_geo == 0:
                 self.app.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n"
-                                      "Usually it means that the tool diameter is too big for the painted geometry.\n"
-                                      "Change the painting parameters and try again."))
+                                       "Usually it means that the tool diameter is too big for the painted geometry.\n"
+                                       "Change the painting parameters and try again."))
                 return
 
             # Experimental...
@@ -1222,7 +1226,7 @@ class ToolPaint(FlatCAMTool, Gerber):
                 self.paint_tools[current_uid]['data']['name'] = name
                 cleared_geo[:] = []
 
-            geo_obj.options["cnctooldia"] = tool_dia
+            geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = True
             geo_obj.multitool = True

+ 1 - 1
flatcamTools/ToolSolderPaste.py

@@ -548,7 +548,7 @@ class SolderPaste(FlatCAMTool):
                     if self.units == 'MM':
                         dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
                     else:
-                        dia = QtWidgets.QTableWidgetItem('%.3f' % tooluid_value['tooldia'])
+                        dia = QtWidgets.QTableWidgetItem('%.4f' % tooluid_value['tooldia'])
 
                     dia.setFlags(QtCore.Qt.ItemIsEnabled)
 

BIN
locale/de/LC_MESSAGES/strings.mo


File diff suppressed because it is too large
+ 202 - 198
locale/de/LC_MESSAGES/strings.po


BIN
locale/en/LC_MESSAGES/strings.mo


File diff suppressed because it is too large
+ 202 - 198
locale/en/LC_MESSAGES/strings.po


BIN
locale/ro/LC_MESSAGES/strings.mo


File diff suppressed because it is too large
+ 199 - 195
locale/ro/LC_MESSAGES/strings.po


BIN
locale/ru/LC_MESSAGES/strings.mo


File diff suppressed because it is too large
+ 282 - 253
locale/ru/LC_MESSAGES/strings.po


File diff suppressed because it is too large
+ 206 - 202
locale_template/strings.pot


Some files were not shown because too many files changed in this diff