Explorar el Código

- small changes to the Toolchange manual preprocessor
- fix for plotting Excellon objects if the color is changed and then the object is moved
- laying the GUI for a new Tool: Punch Gerber Tool which will add holes in the Gerber apertures

Marius Stanciu hace 6 años
padre
commit
ca87475694

+ 5 - 0
FlatCAMApp.py

@@ -2517,6 +2517,7 @@ class App(QtCore.QObject):
         self.fiducial_tool = None
         self.edrills_tool = None
         self.align_objects_tool = None
+        self.punch_tool = None
 
         # always install tools only after the shell is initialized because the self.inform.emit() depends on shell
         try:
@@ -3150,6 +3151,9 @@ class App(QtCore.QObject):
         self.qrcode_tool.install(icon=QtGui.QIcon(self.resource_location + '/qrcode32.png'),
                                  pos=self.ui.menutool)
 
+        self.punch_tool = ToolPunchGerber(self)
+        self.punch_tool.install(icon=QtGui.QIcon(self.resource_location + '/punch32.png'), pos=self.ui.menutool)
+
         self.transform_tool = ToolTransform(self)
         self.transform_tool.install(icon=QtGui.QIcon(self.resource_location + '/transform.png'),
                                     pos=self.ui.menuoptions, separator=True)
@@ -3291,6 +3295,7 @@ class App(QtCore.QObject):
         self.ui.qrcode_btn.triggered.connect(lambda: self.qrcode_tool.run(toggle=True))
         self.ui.copperfill_btn.triggered.connect(lambda: self.copper_thieving_tool.run(toggle=True))
         self.ui.fiducials_btn.triggered.connect(lambda: self.fiducial_tool.run(toggle=True))
+        self.ui.punch_btn.triggered.connect(lambda: self.punch_tool.run(toggle=True))
 
     def object2editor(self):
         """

+ 18 - 5
FlatCAMObj.py

@@ -1004,15 +1004,21 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         def geo_init(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry)
             if isinstance(self.solid_geometry, list):
-                self.solid_geometry = cascaded_union(self.solid_geometry)
+                try:
+                    self.solid_geometry = MultiPolygon(self.solid_geometry)
+                except Exception:
+                    self.solid_geometry = cascaded_union(self.solid_geometry)
 
             bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
             if not self.options["noncopperrounded"]:
                 bounding_box = bounding_box.envelope
             non_copper = bounding_box.difference(self.solid_geometry)
+
+            if non_copper is None or non_copper.is_empty:
+                self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done."))
+                return "fail"
             geo_obj.solid_geometry = non_copper
 
-        # TODO: Check for None
         self.app.new_object("geometry", name, geo_init)
 
     def on_generatebb_button_click(self, *args):
@@ -1024,12 +1030,19 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
             assert isinstance(geo_obj, FlatCAMGeometry)
 
             if isinstance(self.solid_geometry, list):
-                self.solid_geometry = MultiPolygon(self.solid_geometry)
+                try:
+                    self.solid_geometry = MultiPolygon(self.solid_geometry)
+                except Exception:
+                    self.solid_geometry = cascaded_union(self.solid_geometry)
 
             # Bounding box with rounded corners
             bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"]))
             if not self.options["bboxrounded"]:  # Remove rounded corners
                 bounding_box = bounding_box.envelope
+
+            if bounding_box is None or bounding_box.is_empty:
+                self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done."))
+                return "fail"
             geo_obj.solid_geometry = bounding_box
 
         self.app.new_object("geometry", name, geo_init)
@@ -3641,8 +3654,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
             if self.options["solid"]:
                 for geo in self.solid_geometry:
                     self.add_shape(shape=geo,
-                                   color=self.app.defaults["excellon_plot_line"],
-                                   face_color=self.app.defaults["excellon_plot_fill"],
+                                   color=self.outline_color,
+                                   face_color=self.fill_color,
                                    visible=visible,
                                    layer=2)
             else:

+ 6 - 0
README.md

@@ -9,6 +9,12 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+24.02.2020
+
+- small changes to the Toolchange manual preprocessor
+- fix for plotting Excellon objects if the color is changed and then the object is moved
+- laying the GUI for a new Tool: Punch Gerber Tool which will add holes in the Gerber apertures
+
 22.01.2020
 
 - fixed a bug in the bounding box generation

+ 2 - 0
flatcamGUI/FlatCAMGUI.py

@@ -920,6 +920,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
             QtGui.QIcon(self.app.resource_location + '/fiducials_32.png'), _("Fiducials Tool"))
         self.cal_btn = self.toolbartools.addAction(
             QtGui.QIcon(self.app.resource_location + '/calibrate_32.png'), _("Calibration Tool"))
+        self.punch_btn = self.toolbartools.addAction(
+            QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool"))
 
         # ########################################################################
         # ########################## Excellon Editor Toolbar# ####################

+ 2 - 3
flatcamTools/ToolExtractDrills.py

@@ -43,8 +43,7 @@ class ToolExtractDrills(FlatCAMTool):
                         """)
         self.layout.addWidget(title_label)
 
-        self.empty_lb = QtWidgets.QLabel("")
-        self.layout.addWidget(self.empty_lb)
+        self.layout.addWidget(QtWidgets.QLabel(""))
 
         # ## Grid Layout
         grid_lay = QtWidgets.QGridLayout()
@@ -128,7 +127,7 @@ class ToolExtractDrills(FlatCAMTool):
         self.method_label = QtWidgets.QLabel('<b>%s</b>' % _("Method"))
         grid1.addWidget(self.method_label, 2, 0, 1, 2)
 
-        # ## Axis
+        # ## Holes Size
         self.hole_size_radio = RadioSet(
             [
                 {'label': _("Fixed Diameter"), 'value': 'fixed'},

+ 373 - 0
flatcamTools/ToolPunchGerber.py

@@ -0,0 +1,373 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing            #
+# File Author: Marius Adrian Stanciu (c)                   #
+# Date: 1/24/2020                                          #
+# MIT Licence                                              #
+# ##########################################################
+
+from PyQt5 import QtGui, QtCore, QtWidgets
+
+from FlatCAMTool import FlatCAMTool
+from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
+    OptionalHideInputSection, OptionalInputSection, FCComboBox
+
+from copy import deepcopy
+import logging
+from shapely.geometry import Polygon, MultiPolygon, Point
+
+from reportlab.graphics import renderPDF
+from reportlab.pdfgen import canvas
+from reportlab.graphics import renderPM
+from reportlab.lib.units import inch, mm
+from reportlab.lib.pagesizes import landscape, portrait
+
+from svglib.svglib import svg2rlg
+from xml.dom.minidom import parseString as parse_xml_string
+from lxml import etree as ET
+from io import StringIO
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+log = logging.getLogger('base')
+
+
+class ToolPunchGerber(FlatCAMTool):
+
+    toolName = _("Punch Gerber")
+
+    def __init__(self, app):
+        FlatCAMTool.__init__(self, app)
+
+        self.decimals = self.app.decimals
+
+        # Title
+        title_label = QtWidgets.QLabel("%s" % self.toolName)
+        title_label.setStyleSheet("""
+                        QLabel
+                        {
+                            font-size: 16px;
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(title_label)
+
+        # Punch Drill holes
+        self.layout.addWidget(QtWidgets.QLabel(""))
+
+        # ## Grid Layout
+        grid_lay = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid_lay)
+        grid_lay.setColumnStretch(0, 1)
+        grid_lay.setColumnStretch(1, 0)
+
+        # ## Gerber Object
+        self.gerber_object_combo = QtWidgets.QComboBox()
+        self.gerber_object_combo.setModel(self.app.collection)
+        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.gerber_object_combo.setCurrentIndex(1)
+
+        self.grb_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
+        self.grb_label.setToolTip('%s.' % _("Gerber into which to punch holes"))
+
+        grid_lay.addWidget(self.grb_label, 0, 0, 1, 2)
+        grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid_lay.addWidget(separator_line, 2, 0, 1, 2)
+
+        # Grid Layout
+        grid0 = QtWidgets.QGridLayout()
+        self.layout.addLayout(grid0)
+        grid0.setColumnStretch(0, 0)
+        grid0.setColumnStretch(1, 1)
+
+        self.method_label = QtWidgets.QLabel('%s:' % _("Method"))
+        self.method_label.setToolTip(
+            _("The punch hole source can be:\n"
+              "- Excellon -> an Excellon holes center will serve as reference.\n"
+              "- Fixed Diameter -> will try to use the pads center as reference.\n"
+              "- Fixed Annular Ring -> will try to use the pads center as reference.\n"
+              "- Proportional -> will try to use the pads center as reference.\n")
+        )
+        self.method_punch = RadioSet(
+            [
+                {'label': _('Excellon'), 'value': 'exc'},
+                {'label': _("Fixed Diameter"), 'value': 'fixed'},
+                {'label': _("Fixed Annular Ring"), 'value': 'ring'},
+                {'label': _("Proportional"), 'value': 'prop'}
+            ],
+            orientation='vertical',
+            stretch=False)
+        grid0.addWidget(self.method_label, 0, 0)
+        grid0.addWidget(self.method_punch, 0, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 1, 0, 1, 2)
+
+        self.exc_label = QtWidgets.QLabel('<b>%s</b>' % _("Excellon"))
+        self.exc_label.setToolTip(
+            _("Remove the geometry of Excellon from the Gerber to create the holes in pads.")
+        )
+
+        self.exc_combo = QtWidgets.QComboBox()
+        self.exc_combo.setModel(self.app.collection)
+        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
+        self.exc_combo.setCurrentIndex(1)
+
+        grid0.addWidget(self.exc_label, 2, 0, 1, 2)
+        grid0.addWidget(self.exc_combo, 3, 0, 1, 2)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 5, 0, 1, 2)
+
+        # Fixed Dia
+        self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
+        grid0.addWidget(self.fixed_label, 6, 0, 1, 2)
+
+        # Diameter value
+        self.dia_entry = FCDoubleSpinner()
+        self.dia_entry.set_precision(self.decimals)
+        self.dia_entry.set_range(0.0000, 9999.9999)
+
+        self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.dia_label.setToolTip(
+            _("Fixed hole diameter.")
+        )
+
+        grid0.addWidget(self.dia_label, 8, 0)
+        grid0.addWidget(self.dia_entry, 8, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 9, 0, 1, 2)
+
+        self.ring_frame = QtWidgets.QFrame()
+        self.ring_frame.setContentsMargins(0, 0, 0, 0)
+        grid0.addWidget(self.ring_frame, 10, 0, 1, 2)
+
+        self.ring_box = QtWidgets.QVBoxLayout()
+        self.ring_box.setContentsMargins(0, 0, 0, 0)
+        self.ring_frame.setLayout(self.ring_box)
+
+        # ## Grid Layout
+        grid1 = QtWidgets.QGridLayout()
+        grid1.setColumnStretch(0, 0)
+        grid1.setColumnStretch(1, 1)
+        self.ring_box.addLayout(grid1)
+
+        # Annular Ring value
+        self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
+        self.ring_label.setToolTip(
+            _("The size of annular ring.\n"
+              "The copper sliver between the drill hole exterior\n"
+              "and the margin of the copper pad.")
+        )
+        grid1.addWidget(self.ring_label, 0, 0, 1, 2)
+
+        # Circular Annular Ring Value
+        self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
+        self.circular_ring_label.setToolTip(
+            _("The size of annular ring for circular pads.")
+        )
+
+        self.circular_ring_entry = FCDoubleSpinner()
+        self.circular_ring_entry.set_precision(self.decimals)
+        self.circular_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid1.addWidget(self.circular_ring_label, 1, 0)
+        grid1.addWidget(self.circular_ring_entry, 1, 1)
+
+        # Oblong Annular Ring Value
+        self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
+        self.oblong_ring_label.setToolTip(
+            _("The size of annular ring for oblong pads.")
+        )
+
+        self.oblong_ring_entry = FCDoubleSpinner()
+        self.oblong_ring_entry.set_precision(self.decimals)
+        self.oblong_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid1.addWidget(self.oblong_ring_label, 2, 0)
+        grid1.addWidget(self.oblong_ring_entry, 2, 1)
+
+        # Square Annular Ring Value
+        self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
+        self.square_ring_label.setToolTip(
+            _("The size of annular ring for square pads.")
+        )
+
+        self.square_ring_entry = FCDoubleSpinner()
+        self.square_ring_entry.set_precision(self.decimals)
+        self.square_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid1.addWidget(self.square_ring_label, 3, 0)
+        grid1.addWidget(self.square_ring_entry, 3, 1)
+
+        # Rectangular Annular Ring Value
+        self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
+        self.rectangular_ring_label.setToolTip(
+            _("The size of annular ring for rectangular pads.")
+        )
+
+        self.rectangular_ring_entry = FCDoubleSpinner()
+        self.rectangular_ring_entry.set_precision(self.decimals)
+        self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid1.addWidget(self.rectangular_ring_label, 4, 0)
+        grid1.addWidget(self.rectangular_ring_entry, 4, 1)
+
+        # Others Annular Ring Value
+        self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
+        self.other_ring_label.setToolTip(
+            _("The size of annular ring for other pads.")
+        )
+
+        self.other_ring_entry = FCDoubleSpinner()
+        self.other_ring_entry.set_precision(self.decimals)
+        self.other_ring_entry.set_range(0.0000, 9999.9999)
+
+        grid1.addWidget(self.other_ring_label, 5, 0)
+        grid1.addWidget(self.other_ring_entry, 5, 1)
+
+        separator_line = QtWidgets.QFrame()
+        separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line, 11, 0, 1, 2)
+
+        # Proportional value
+        self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
+        grid0.addWidget(self.prop_label, 12, 0, 1, 2)
+
+        # Diameter value
+        self.factor_entry = FCDoubleSpinner(suffix='%')
+        self.factor_entry.set_precision(self.decimals)
+        self.factor_entry.set_range(0.0000, 100.0000)
+        self.factor_entry.setSingleStep(0.1)
+
+        self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
+        self.factor_label.setToolTip(
+            _("Proportional Diameter.\n"
+              "The drill diameter will be a fraction of the pad size.")
+        )
+
+        grid0.addWidget(self.factor_label, 13, 0)
+        grid0.addWidget(self.factor_entry, 13, 1)
+
+        separator_line3 = QtWidgets.QFrame()
+        separator_line3.setFrameShape(QtWidgets.QFrame.HLine)
+        separator_line3.setFrameShadow(QtWidgets.QFrame.Sunken)
+        grid0.addWidget(separator_line3, 14, 0, 1, 2)
+
+        # Buttons
+        self.punch_object_button = QtWidgets.QPushButton(_("Punch Gerber"))
+        self.punch_object_button.setToolTip(
+            _("Create a Gerber object from the selected object, within\n"
+              "the specified box.")
+        )
+        self.punch_object_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.punch_object_button)
+
+        self.layout.addStretch()
+
+        # ## Reset Tool
+        self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
+        self.reset_button.setToolTip(
+            _("Will reset the tool parameters.")
+        )
+        self.reset_button.setStyleSheet("""
+                        QPushButton
+                        {
+                            font-weight: bold;
+                        }
+                        """)
+        self.layout.addWidget(self.reset_button)
+
+        self.units = self.app.defaults['units']
+
+        # ## Signals
+
+        self.method_punch.activated_custom.connect(self.on_method)
+        self.reset_button.clicked.connect(self.set_tool_ui)
+
+    def run(self, toggle=True):
+        self.app.report_usage("ToolPunchGerber()")
+
+        if toggle:
+            # if the splitter is hidden, display it, else hide it but only if the current widget is the same
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+            else:
+                try:
+                    if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
+                        # if tab is populated with the tool but it does not have the focus, focus on it
+                        if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
+                            # focus on Tool Tab
+                            self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
+                        else:
+                            self.app.ui.splitter.setSizes([0, 1])
+                except AttributeError:
+                    pass
+        else:
+            if self.app.ui.splitter.sizes()[0] == 0:
+                self.app.ui.splitter.setSizes([1, 1])
+
+        FlatCAMTool.run(self)
+
+        self.set_tool_ui()
+
+        self.app.ui.notebook.setTabText(2, _("Punch Tool"))
+
+    def install(self, icon=None, separator=None, **kwargs):
+        FlatCAMTool.install(self, icon, separator, shortcut='ALT+H', **kwargs)
+
+    def set_tool_ui(self):
+        self.reset_fields()
+
+        self.method_punch.set_value('exc')
+
+    def on_method(self, val):
+        self.exc_label.setEnabled(False)
+        self.exc_combo.setEnabled(False)
+        self.fixed_label.setEnabled(False)
+        self.dia_label.setEnabled(False)
+        self.dia_entry.setEnabled(False)
+        self.ring_frame.setEnabled(False)
+        self.prop_label.setEnabled(False)
+        self.factor_label.setEnabled(False)
+        self.factor_entry.setEnabled(False)
+
+        if val == 'exc':
+            self.exc_label.setEnabled(True)
+            self.exc_combo.setEnabled(True)
+        elif val == 'fixed':
+            self.fixed_label.setEnabled(True)
+            self.dia_label.setEnabled(True)
+            self.dia_entry.setEnabled(True)
+        elif val == 'ring':
+            self.ring_frame.setEnabled(True)
+        elif val == 'prop':
+            self.prop_label.setEnabled(True)
+            self.factor_label.setEnabled(True)
+            self.factor_entry.setEnabled(True)
+
+    def reset_fields(self):
+        self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))

+ 1 - 0
flatcamTools/__init__.py

@@ -38,3 +38,4 @@ from flatcamTools.ToolSolderPaste import SolderPaste
 from flatcamTools.ToolSub import ToolSub
 
 from flatcamTools.ToolTransform import ToolTransform
+from flatcamTools.ToolPunchGerber import ToolPunchGerber

+ 2 - 0
preprocessors/Toolchange_manual.py

@@ -119,6 +119,7 @@ M0
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
+(MSG, Drilling with Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 """.format(x_toolchange=self.coordinate_format % (p.coords_decimals, x_toolchange),
            y_toolchange=self.coordinate_format % (p.coords_decimals, y_toolchange),
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
@@ -139,6 +140,7 @@ M0
 G00 Z{z_toolchange}
 (MSG, Now the tool can be tightened more securely.)
 M0
+(MSG, Milling with Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
 """.format(
            z_toolchange=self.coordinate_format % (p.coords_decimals, z_toolchange),
            tool=int(p.tool),

BIN
share/punch16.png


BIN
share/punch32.png