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

- in Tool Transform added a new feature named 'Buffer'. For Geometry and Gerber objects will create (and replace) a geometry at a distance from the original geometry and for Excellon will adjust the Tool diameters

Marius Stanciu 6 лет назад
Родитель
Сommit
b1b140634b
9 измененных файлов с 359 добавлено и 33 удалено
  1. 4 0
      FlatCAMApp.py
  2. 14 3
      FlatCAMObj.py
  3. 0 5
      ObjectCollection.py
  4. 2 1
      README.md
  5. 63 0
      camlib.py
  6. 44 6
      flatcamGUI/PreferencesUI.py
  7. 32 1
      flatcamParsers/ParseExcellon.py
  8. 81 0
      flatcamParsers/ParseGerber.py
  9. 119 17
      flatcamTools/ToolTransform.py

+ 4 - 0
FlatCAMApp.py

@@ -830,6 +830,8 @@ class App(QtCore.QObject):
             "tools_transform_offset_y": 0.0,
             "tools_transform_offset_y": 0.0,
             "tools_transform_mirror_reference": False,
             "tools_transform_mirror_reference": False,
             "tools_transform_mirror_point": (0, 0),
             "tools_transform_mirror_point": (0, 0),
+            "tools_transform_buffer_dis": 0.0,
+            "tools_transform_buffer_corner": True,
 
 
             # SolderPaste Tool
             # SolderPaste Tool
             "tools_solderpaste_tools": "1.0, 0.3",
             "tools_solderpaste_tools": "1.0, 0.3",
@@ -1432,6 +1434,8 @@ class App(QtCore.QObject):
             "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry,
             "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry,
             "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb,
             "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb,
             "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry,
             "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry,
+            "tools_transform_buffer_dis": self.ui.tools_defaults_form.tools_transform_group.buffer_entry,
+            "tools_transform_buffer_corner": self.ui.tools_defaults_form.tools_transform_group.buffer_rounded_cb,
 
 
             # SolderPaste Dispensing Tool
             # SolderPaste Dispensing Tool
             "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry,
             "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry,

+ 14 - 3
FlatCAMObj.py

@@ -599,7 +599,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
     def __init__(self, name):
     def __init__(self, name):
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"]))
+        self.circle_steps = int(self.app.defaults["gerber_circle_steps"])
+
+        Gerber.__init__(self, steps_per_circle=self.circle_steps)
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
         self.kind = "gerber"
         self.kind = "gerber"
@@ -2199,6 +2201,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
         Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
         Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point)
         self.replotApertures.emit()
         self.replotApertures.emit()
 
 
+    def buffer(self, distance, join):
+        Gerber.buffer(self, distance=distance, join=join)
+        self.replotApertures.emit()
+
     def serialize(self):
     def serialize(self):
         return {
         return {
             "options": self.options,
             "options": self.options,
@@ -2217,7 +2223,9 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
     def __init__(self, name):
     def __init__(self, name):
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
 
 
-        Excellon.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"]))
+        self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
+
+        Excellon.__init__(self, geo_steps_per_circle=self.circle_steps)
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
 
 
         self.kind = "excellon"
         self.kind = "excellon"
@@ -3545,8 +3553,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
 
 
     def __init__(self, name):
     def __init__(self, name):
         self.decimals = self.app.decimals
         self.decimals = self.app.decimals
+
+        self.circle_steps = int(self.app.defaults["geometry_circle_steps"])
+
         FlatCAMObj.__init__(self, name)
         FlatCAMObj.__init__(self, name)
-        Geometry.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"]))
+        Geometry.__init__(self, geo_steps_per_circle=self.circle_steps)
 
 
         self.kind = "geometry"
         self.kind = "geometry"
 
 

+ 0 - 5
ObjectCollection.py

@@ -787,11 +787,6 @@ class ObjectCollection(QtCore.QAbstractItemModel):
         # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
         # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
 
 
         try:
         try:
-            # delete selection shape
-            self.app.delete_selection_shape()
-            for o in self.get_list():
-                o.selection_shape_drawn = False
-
             obj = current.indexes()[0].internalPointer().obj
             obj = current.indexes()[0].internalPointer().obj
             self.item_selected.emit(obj.options['name'])
             self.item_selected.emit(obj.options['name'])
 
 

+ 2 - 1
README.md

@@ -17,8 +17,9 @@ CAD program, and create G-Code for Isolation routing.
 - speed up the plotting in OpenGL(3D) graphic mode
 - speed up the plotting in OpenGL(3D) graphic mode
 - spped up the color setting for Gerber object when using the OpenGL(3D) graphic mode
 - spped up the color setting for Gerber object when using the OpenGL(3D) graphic mode
 - setting color for Gerber objects work on a selection of Gerber objects
 - setting color for Gerber objects work on a selection of Gerber objects
-- when the selection is changed in the Project Tree the selection shape on canvas is deleted
+- ~~when the selection is changed in the Project Tree the selection shape on canvas is deleted~~
 - if an object is selected on Project Tree and it does not have the selection shape drawn, first click on canvas over it will draw the selection shape 
 - if an object is selected on Project Tree and it does not have the selection shape drawn, first click on canvas over it will draw the selection shape 
+- in Tool Transform added a new feature named 'Buffer'. For Geometry and Gerber objects will create (and replace) a geometry at a distance from the original geometry and for Excellon will adjust the Tool diameters
 
 
 22.12.2019
 22.12.2019
 
 

+ 63 - 0
camlib.py

@@ -2118,6 +2118,69 @@ class Geometry(object):
         #     self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
         #     self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
         #                                         origin=(px, py))
         #                                         origin=(px, py))
 
 
+    def buffer(self, distance, join):
+        """
+
+        :param distance:
+        :param join:
+        :return:
+        """
+
+        log.debug("camlib.Geometry.buffer()")
+
+        if distance == 0:
+            return
+
+        def buffer_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(buffer_geom(g))
+                return new_obj
+            else:
+                try:
+                    self.el_count += 1
+                    disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
+                    if self.old_disp_number < disp_number <= 100:
+                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                        self.old_disp_number = disp_number
+
+                    return obj.buffer(distance, resolution=self.geo_steps_per_circle, join_style=join)
+                except AttributeError:
+                    return obj
+
+        try:
+            if self.multigeo is True:
+                for tool in self.tools:
+                    # variables to display the percentage of work done
+                    self.geo_len = 0
+                    try:
+                        for __ in self.tools[tool]['solid_geometry']:
+                            self.geo_len += 1
+                    except TypeError:
+                        self.geo_len = 1
+                    self.old_disp_number = 0
+                    self.el_count = 0
+
+                    self.tools[tool]['solid_geometry'] = buffer_geom(self.tools[tool]['solid_geometry'])
+
+            # variables to display the percentage of work done
+            self.geo_len = 0
+            try:
+                for __ in self.solid_geometry:
+                    self.geo_len += 1
+            except TypeError:
+                self.geo_len = 1
+            self.old_disp_number = 0
+            self.el_count = 0
+
+            self.solid_geometry = buffer_geom(self.solid_geometry)
+
+            self.app.inform.emit('[success] %s...' %  _('Object was buffered'))
+        except AttributeError:
+            self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to buffer. No object selected"))
+
+        self.app.proc_container.new_text = ''
 
 
 class AttrDict(dict):
 class AttrDict(dict):
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):

+ 44 - 6
flatcamGUI/PreferencesUI.py

@@ -5378,7 +5378,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.skewy_label, 4, 0)
         grid0.addWidget(self.skewy_label, 4, 0)
         grid0.addWidget(self.skewy_entry, 4, 1)
         grid0.addWidget(self.skewy_entry, 4, 1)
 
 
-        # ## Scale factor on X axis
+        # ## Scale
         scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale"))
         scale_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale"))
         grid0.addWidget(scale_title_lbl, 5, 0, 1, 2)
         grid0.addWidget(scale_title_lbl, 5, 0, 1, 2)
 
 
@@ -5425,7 +5425,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         )
         )
         grid0.addWidget(self.reference_cb, 8, 1)
         grid0.addWidget(self.reference_cb, 8, 1)
 
 
-        # ## Offset distance on X axis
+        # ## Offset
         offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset"))
         offset_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Offset"))
         grid0.addWidget(offset_title_lbl, 9, 0, 1, 2)
         grid0.addWidget(offset_title_lbl, 9, 0, 1, 2)
 
 
@@ -5454,6 +5454,10 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         grid0.addWidget(self.offy_label, 11, 0)
         grid0.addWidget(self.offy_label, 11, 0)
         grid0.addWidget(self.offy_entry, 11, 1)
         grid0.addWidget(self.offy_entry, 11, 1)
 
 
+        # ## Mirror
+        mirror_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Mirror"))
+        grid0.addWidget(mirror_title_lbl, 12, 0, 1, 2)
+
         # ## Mirror (Flip) Reference Point
         # ## Mirror (Flip) Reference Point
         self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
         self.mirror_reference_cb = FCCheckBox('%s' % _("Mirror Reference"))
         self.mirror_reference_cb.setToolTip(
         self.mirror_reference_cb.setToolTip(
@@ -5466,9 +5470,9 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
               "Then click Add button to insert coordinates.\n"
               "Then click Add button to insert coordinates.\n"
               "Or enter the coords in format (x, y) in the\n"
               "Or enter the coords in format (x, y) in the\n"
               "Point Entry field and click Flip on X(Y)"))
               "Point Entry field and click Flip on X(Y)"))
-        grid0.addWidget(self.mirror_reference_cb, 12, 0, 1, 2)
+        grid0.addWidget(self.mirror_reference_cb, 13, 0, 1, 2)
 
 
-        self.flip_ref_label = QtWidgets.QLabel('<b>%s</b>' % _("Mirror Reference point"))
+        self.flip_ref_label = QtWidgets.QLabel('%s' % _("Mirror Reference point"))
         self.flip_ref_label.setToolTip(
         self.flip_ref_label.setToolTip(
             _("Coordinates in format (x, y) used as reference for mirroring.\n"
             _("Coordinates in format (x, y) used as reference for mirroring.\n"
               "The 'x' in (x, y) will be used when using Flip on X and\n"
               "The 'x' in (x, y) will be used when using Flip on X and\n"
@@ -5476,8 +5480,42 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
         )
         )
         self.flip_ref_entry = EvalEntry2("(0, 0)")
         self.flip_ref_entry = EvalEntry2("(0, 0)")
 
 
-        grid0.addWidget(self.flip_ref_label, 13, 0, 1, 2)
-        grid0.addWidget(self.flip_ref_entry, 14, 0, 1, 2)
+        grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
+        grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)
+
+        # ## Buffer
+        buffer_title_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Buffer"))
+        grid0.addWidget(buffer_title_lbl, 16, 0, 1, 2)
+
+        self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
+        self.buffer_label.setToolTip(
+            _("A positive value will create the effect of dilation,\n"
+              "while a negative value will create the effect of erosion.\n"
+              "Each geometry element of the object will be increased\n"
+              "or decreased with the 'distance'.")
+        )
+
+        self.buffer_entry = FCDoubleSpinner()
+        self.buffer_entry.set_precision(self.decimals)
+        self.buffer_entry.setSingleStep(0.1)
+        self.buffer_entry.setWrapping(True)
+        self.buffer_entry.set_range(-9999.9999, 9999.9999)
+
+        grid0.addWidget(self.buffer_label, 17, 0)
+        grid0.addWidget(self.buffer_entry, 17, 1)
+
+        self.buffer_rounded_cb = FCCheckBox()
+        self.buffer_rounded_cb.setText('%s' % _("Rounded"))
+        self.buffer_rounded_cb.setToolTip(
+            _("If checked then the buffer will surround the buffered shape,\n"
+              "every corner will be rounded.\n"
+              "If not checked then the buffer will follow the exact geometry\n"
+              "of the buffered shape.")
+        )
+
+        grid0.addWidget(self.buffer_rounded_cb, 18, 0, 1, 2)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 19, 0, 1, 2)
 
 
         self.layout.addStretch()
         self.layout.addStretch()
 
 

+ 32 - 1
flatcamParsers/ParseExcellon.py

@@ -1457,4 +1457,35 @@ class Excellon(Geometry):
                 slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py))
                 slot['start'] = affinity.rotate(slot['start'], angle, origin=(px, py))
 
 
         self.create_geometry()
         self.create_geometry()
-        self.app.proc_container.new_text = ''
+        self.app.proc_container.new_text = ''
+
+    def buffer(self, distance, join):
+        """
+
+        :param distance:
+        :param join:
+        :return:
+        """
+        log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()")
+
+        if distance == 0:
+            return
+
+        def buffer_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(buffer_geom(g))
+                return new_obj
+            else:
+                try:
+                    return obj.buffer(distance, resolution=self.geo_steps_per_circle)
+                except AttributeError:
+                    return obj
+
+        # buffer solid_geometry
+        for tool, tool_dict in list(self.tools.items()):
+            self.tools[tool]['solid_geometry'] = buffer_geom(tool_dict['solid_geometry'])
+            self.tools[tool]['C'] += distance
+
+        self.create_geometry()

+ 81 - 0
flatcamParsers/ParseGerber.py

@@ -2169,6 +2169,87 @@ class Gerber(Geometry):
                              _("Gerber Rotate done."))
                              _("Gerber Rotate done."))
         self.app.proc_container.new_text = ''
         self.app.proc_container.new_text = ''
 
 
+    def buffer(self, distance, join):
+        """
+
+        :param distance:
+        :return:
+        """
+        log.debug("parseGerber.Gerber.buffer()")
+
+        if distance == 0:
+            return
+
+        # variables to display the percentage of work done
+        self.geo_len = 0
+        try:
+            for __ in self.solid_geometry:
+                self.geo_len += 1
+        except TypeError:
+            self.geo_len = 1
+
+        self.old_disp_number = 0
+        self.el_count = 0
+
+        def buffer_geom(obj):
+            if type(obj) is list:
+                new_obj = []
+                for g in obj:
+                    new_obj.append(buffer_geom(g))
+                return new_obj
+            else:
+                try:
+                    self.el_count += 1
+                    disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100]))
+                    if self.old_disp_number < disp_number <= 100:
+                        self.app.proc_container.update_view_text(' %d%%' % disp_number)
+                        self.old_disp_number = disp_number
+
+                    return obj.buffer(distance, resolution=self.steps_per_circle, join_style=join)
+                except AttributeError:
+                    return obj
+
+        self.solid_geometry = buffer_geom(self.solid_geometry)
+
+        # we need to buffer the geometry stored in the Gerber apertures, too
+        try:
+            for apid in self.apertures:
+                new_geometry = list()
+                if 'geometry' in self.apertures[apid]:
+                    for geo_el in self.apertures[apid]['geometry']:
+                        new_geo_el = dict()
+                        if 'solid' in geo_el:
+                            new_geo_el['solid'] = buffer_geom(geo_el['solid'])
+                        if 'follow' in geo_el:
+                            new_geo_el['follow'] = buffer_geom(geo_el['follow'])
+                        if 'clear' in geo_el:
+                            new_geo_el['clear'] = buffer_geom(geo_el['clear'])
+                        new_geometry.append(new_geo_el)
+
+                self.apertures[apid]['geometry'] = deepcopy(new_geometry)
+
+                try:
+                    if str(self.apertures[apid]['type']) == 'R' or str(self.apertures[apid]['type']) == 'O':
+                        self.apertures[apid]['width'] += (distance * 2)
+                        self.apertures[apid]['height'] += (distance * 2)
+                    elif str(self.apertures[apid]['type']) == 'P':
+                        self.apertures[apid]['diam'] += (distance * 2)
+                        self.apertures[apid]['nVertices'] += (distance * 2)
+                except KeyError:
+                    pass
+
+                try:
+                    if self.apertures[apid]['size'] is not None:
+                        self.apertures[apid]['size'] = float(self.apertures[apid]['size'] + (distance * 2))
+                except KeyError:
+                    pass
+        except Exception as e:
+            log.debug('camlib.Gerber.buffer() Exception --> %s' % str(e))
+            return 'fail'
+
+        self.app.inform.emit('[success] %s' % _("Gerber Buffer done."))
+        self.app.proc_container.new_text = ''
+
 
 
 def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
 def parse_gerber_number(strnumber, int_digits, frac_digits, zeros):
     """
     """

+ 119 - 17
flatcamTools/ToolTransform.py

@@ -27,6 +27,7 @@ class ToolTransform(FlatCAMTool):
     scaleName = _("Scale")
     scaleName = _("Scale")
     flipName = _("Mirror (Flip)")
     flipName = _("Mirror (Flip)")
     offsetName = _("Offset")
     offsetName = _("Offset")
+    bufferName = _("Buffer")
 
 
     def __init__(self, app):
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)
         FlatCAMTool.__init__(self, app)
@@ -255,11 +256,11 @@ class ToolTransform(FlatCAMTool):
         grid0.addWidget(self.offy_entry, 14, 1)
         grid0.addWidget(self.offy_entry, 14, 1)
         grid0.addWidget(self.offy_button, 14, 2)
         grid0.addWidget(self.offy_button, 14, 2)
 
 
-        grid0.addWidget(QtWidgets.QLabel(''))
+        grid0.addWidget(QtWidgets.QLabel(''), 15, 0, 1, 3)
 
 
         # ## Flip Title
         # ## Flip Title
         flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
         flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
-        self.transform_lay.addWidget(flip_title_label)
+        grid0.addWidget(flip_title_label, 16, 0, 1, 3)
 
 
         self.flipx_button = FCButton()
         self.flipx_button = FCButton()
         self.flipx_button.set_value(_("Flip on X"))
         self.flipx_button.set_value(_("Flip on X"))
@@ -274,7 +275,7 @@ class ToolTransform(FlatCAMTool):
         )
         )
 
 
         hlay0 = QtWidgets.QHBoxLayout()
         hlay0 = QtWidgets.QHBoxLayout()
-        self.transform_lay.addLayout(hlay0)
+        grid0.addLayout(hlay0, 17, 0, 1, 3)
 
 
         hlay0.addWidget(self.flipx_button)
         hlay0.addWidget(self.flipx_button)
         hlay0.addWidget(self.flipy_button)
         hlay0.addWidget(self.flipy_button)
@@ -293,7 +294,7 @@ class ToolTransform(FlatCAMTool):
               "Or enter the coords in format (x, y) in the\n"
               "Or enter the coords in format (x, y) in the\n"
               "Point Entry field and click Flip on X(Y)"))
               "Point Entry field and click Flip on X(Y)"))
 
 
-        self.transform_lay.addWidget(self.flip_ref_cb)
+        grid0.addWidget(self.flip_ref_cb, 18, 0, 1, 3)
 
 
         self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point"))
         self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point"))
         self.flip_ref_label.setToolTip(
         self.flip_ref_label.setToolTip(
@@ -315,12 +316,60 @@ class ToolTransform(FlatCAMTool):
         self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
         self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
 
 
         hlay1 = QtWidgets.QHBoxLayout()
         hlay1 = QtWidgets.QHBoxLayout()
-        self.transform_lay.addLayout(hlay1)
+        grid0.addLayout(hlay1, 19, 0, 1, 3)
 
 
         hlay1.addWidget(self.flip_ref_label)
         hlay1.addWidget(self.flip_ref_label)
         hlay1.addWidget(self.flip_ref_entry)
         hlay1.addWidget(self.flip_ref_entry)
 
 
-        self.transform_lay.addWidget(self.flip_ref_button)
+        grid0.addWidget(self.flip_ref_button, 20, 0, 1, 3)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 21, 0, 1, 3)
+
+        # ## Buffer Title
+        buffer_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.bufferName)
+        grid0.addWidget(buffer_title_label, 22, 0, 1, 3)
+
+        self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance"))
+        self.buffer_label.setToolTip(
+            _("A positive value will create the effect of dilation,\n"
+              "while a negative value will create the effect of erosion.\n"
+              "Each geometry element of the object will be increased\n"
+              "or decreased with the 'distance'.")
+        )
+
+        self.buffer_entry = FCDoubleSpinner()
+        self.buffer_entry.set_precision(self.decimals)
+        self.buffer_entry.setSingleStep(0.1)
+        self.buffer_entry.setWrapping(True)
+        self.buffer_entry.set_range(-9999.9999, 9999.9999)
+
+        # self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+
+        self.buffer_button = FCButton()
+        self.buffer_button.set_value(_("Buffer"))
+        self.buffer_button.setToolTip(
+            _("Create the buffer effect on each geometry,\n"
+              "element from the selected object.")
+        )
+        self.buffer_button.setMinimumWidth(90)
+
+        grid0.addWidget(self.buffer_label, 23, 0)
+        grid0.addWidget(self.buffer_entry, 23, 1)
+        grid0.addWidget(self.buffer_button, 23, 2)
+
+        self.buffer_rounded_cb = FCCheckBox()
+        self.buffer_rounded_cb.setText('%s' % _("Rounded"))
+        self.buffer_rounded_cb.setToolTip(
+            _("If checked then the buffer will surround the buffered shape,\n"
+              "every corner will be rounded.\n"
+              "If not checked then the buffer will follow the exact geometry\n"
+              "of the buffered shape.")
+        )
+
+        grid0.addWidget(self.buffer_rounded_cb, 24, 0, 1, 3)
+
+        grid0.addWidget(QtWidgets.QLabel(''), 25, 0, 1, 3)
+
         self.transform_lay.addStretch()
         self.transform_lay.addStretch()
 
 
         # ## Signals
         # ## Signals
@@ -334,14 +383,16 @@ class ToolTransform(FlatCAMTool):
         self.flipx_button.clicked.connect(self.on_flipx)
         self.flipx_button.clicked.connect(self.on_flipx)
         self.flipy_button.clicked.connect(self.on_flipy)
         self.flipy_button.clicked.connect(self.on_flipy)
         self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
         self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
+        self.buffer_button.clicked.connect(self.on_buffer)
 
 
-        self.rotate_entry.returnPressed.connect(self.on_rotate)
-        self.skewx_entry.returnPressed.connect(self.on_skewx)
-        self.skewy_entry.returnPressed.connect(self.on_skewy)
-        self.scalex_entry.returnPressed.connect(self.on_scalex)
-        self.scaley_entry.returnPressed.connect(self.on_scaley)
-        self.offx_entry.returnPressed.connect(self.on_offx)
-        self.offy_entry.returnPressed.connect(self.on_offy)
+        # self.rotate_entry.returnPressed.connect(self.on_rotate)
+        # self.skewx_entry.returnPressed.connect(self.on_skewx)
+        # self.skewy_entry.returnPressed.connect(self.on_skewy)
+        # self.scalex_entry.returnPressed.connect(self.on_scalex)
+        # self.scaley_entry.returnPressed.connect(self.on_scaley)
+        # self.offx_entry.returnPressed.connect(self.on_offx)
+        # self.offy_entry.returnPressed.connect(self.on_offy)
+        # self.buffer_entry.returnPressed.connect(self.on_buffer)
 
 
     def run(self, toggle=True):
     def run(self, toggle=True):
         self.app.report_usage("ToolTransform()")
         self.app.report_usage("ToolTransform()")
@@ -430,6 +481,16 @@ class ToolTransform(FlatCAMTool):
         else:
         else:
             self.flip_ref_entry.set_value((0, 0))
             self.flip_ref_entry.set_value((0, 0))
 
 
+        if self.app.defaults["tools_transform_buffer_dis"]:
+            self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"])
+        else:
+            self.buffer_entry.set_value(0.0)
+
+        if self.app.defaults["tools_transform_buffer_corner"]:
+            self.buffer_rounded_cb.set_value(self.app.defaults["tools_transform_buffer_corner"])
+        else:
+            self.buffer_rounded_cb.set_value(True)
+
     def on_rotate(self):
     def on_rotate(self):
         value = float(self.rotate_entry.get_value())
         value = float(self.rotate_entry.get_value())
         if value == 0:
         if value == 0:
@@ -511,8 +572,7 @@ class ToolTransform(FlatCAMTool):
     def on_offx(self):
     def on_offx(self):
         value = float(self.offx_entry.get_value())
         value = float(self.offx_entry.get_value())
         if value == 0:
         if value == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Offset transformation can not be done for a value of 0."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
             return
             return
         axis = 'X'
         axis = 'X'
 
 
@@ -522,14 +582,20 @@ class ToolTransform(FlatCAMTool):
     def on_offy(self):
     def on_offy(self):
         value = float(self.offy_entry.get_value())
         value = float(self.offy_entry.get_value())
         if value == 0:
         if value == 0:
-            self.app.inform.emit('[WARNING_NOTCL] %s' %
-                                 _("Offset transformation can not be done for a value of 0."))
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0."))
             return
             return
         axis = 'Y'
         axis = 'Y'
 
 
         self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
         self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
         return
         return
 
 
+    def on_buffer(self):
+        value = self.buffer_entry.get_value()
+        join = 1 if self.buffer_rounded_cb.get_value() else 2
+
+        self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join]})
+        return
+
     def on_rotate_action(self, num):
     def on_rotate_action(self, num):
         obj_list = self.app.collection.get_selected()
         obj_list = self.app.collection.get_selected()
         xminlist = []
         xminlist = []
@@ -808,4 +874,40 @@ class ToolTransform(FlatCAMTool):
                                          (_("Due of"), str(e),  _("action was not executed.")))
                                          (_("Due of"), str(e),  _("action was not executed.")))
                     return
                     return
 
 
+    def on_buffer_action(self, value, join):
+        obj_list = self.app.collection.get_selected()
+
+        if not obj_list:
+            self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to buffer!"))
+            return
+        else:
+            with self.app.proc_container.new(_("Applying Buffer")):
+                try:
+                    for sel_obj in obj_list:
+                        if isinstance(sel_obj, FlatCAMCNCjob):
+                            self.app.inform.emit(_("CNCJob objects can't be buffered."))
+                        elif sel_obj.kind.lower() == 'gerber':
+                            sel_obj.buffer(value, join)
+                            sel_obj.source_file = self.app.export_gerber(obj_name=sel_obj.options['name'],
+                                                                         filename=None, local_use=sel_obj,
+                                                                         use_thread=False)
+                        elif sel_obj.kind.lower() == 'excellon':
+                            sel_obj.buffer(value, join)
+                            sel_obj.source_file = self.app.export_excellon(obj_name=sel_obj.options['name'],
+                                                                           filename=None, local_use=sel_obj,
+                                                                           use_thread=False)
+                        elif sel_obj.kind.lower() == 'geometry':
+                            sel_obj.buffer(value, join)
+
+                        self.app.object_changed.emit(sel_obj)
+                        sel_obj.plot()
+
+                    self.app.inform.emit('[success] %s...' % _('Buffer done'))
+
+                except Exception as e:
+                    self.app.log.debug("ToolTransform.on_buffer_action() --> %s" % str(e))
+                    self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' %
+                                         (_("Due of"), str(e),  _("action was not executed.")))
+                    return
+
 # end of file
 # end of file