diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index e3604d28..d8a05d53 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -504,6 +504,7 @@ class App(QtCore.QObject):
# CutOut Tool
"tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,
+ "tools_cutoutkind": self.ui.tools_defaults_form.tools_cutout_group.obj_kind_combo,
"tools_cutoutmargin": self.ui.tools_defaults_form.tools_cutout_group.cutout_margin_entry,
"tools_cutoutgapsize": self.ui.tools_defaults_form.tools_cutout_group.cutout_gap_entry,
"tools_gaps_ff": self.ui.tools_defaults_form.tools_cutout_group.gaps_combo,
@@ -839,6 +840,7 @@ class App(QtCore.QObject):
"tools_nccrest": False,
"tools_cutouttooldia": 0.00393701,
+ "tools_cutoutkind": "single",
"tools_cutoutmargin": 0.00393701,
"tools_cutoutgapsize": 0.005905512,
"tools_gaps_ff": "8",
diff --git a/README.md b/README.md
index d6cc2a60..80d40028 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+20.07.2019
+
+- updated the CutOut tool so it will work on single PCB Gerbers or on PCB panel Gerbers
+
19.07.2019
- fixed bug in FlatCAMObj.FlatCAMGeometry.ui_disconnect(); the widgets signals were not disconnected from handlers when required therefore the signals were connected in an exponential way
diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py
index cd6272b1..bf64b5b3 100644
--- a/flatcamGUI/FlatCAMGUI.py
+++ b/flatcamGUI/FlatCAMGUI.py
@@ -5909,14 +5909,29 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
self.cutout_tooldia_entry = LengthEntry()
grid0.addWidget(self.cutout_tooldia_entry, 0, 1)
+ # Object kind
+ kindlabel = QtWidgets.QLabel(_('Obj kind:'))
+ kindlabel.setToolTip(
+ _("Choice of what kind the object we want to cutout is.
"
+ "- Single: contain a single PCB Gerber outline object.
"
+ "- Panel: a panel PCB Gerber object, which is made\n"
+ "out of many individual PCB outlines.")
+ )
+ grid0.addWidget(kindlabel, 1, 0)
+ self.obj_kind_combo = RadioSet([
+ {"label": _("Single"), "value": "single"},
+ {"label": _("Panel"), "value": "panel"},
+ ])
+ grid0.addWidget(self.obj_kind_combo, 1, 1)
+
marginlabel = QtWidgets.QLabel(_('Margin:'))
marginlabel.setToolTip(
_("Distance from objects at which\n"
"to draw the cutout.")
)
- grid0.addWidget(marginlabel, 1, 0)
+ grid0.addWidget(marginlabel, 2, 0)
self.cutout_margin_entry = LengthEntry()
- grid0.addWidget(self.cutout_margin_entry, 1, 1)
+ grid0.addWidget(self.cutout_margin_entry, 2, 1)
gaplabel = QtWidgets.QLabel(_('Gap size:'))
gaplabel.setToolTip(
@@ -5924,9 +5939,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
"that will remain to hold the\n"
"board in place.")
)
- grid0.addWidget(gaplabel, 2, 0)
+ grid0.addWidget(gaplabel, 3, 0)
self.cutout_gap_entry = LengthEntry()
- grid0.addWidget(self.cutout_gap_entry, 2, 1)
+ grid0.addWidget(self.cutout_gap_entry, 3, 1)
gaps_label = QtWidgets.QLabel(_('Gaps:'))
gaps_label.setToolTip(
@@ -5940,9 +5955,9 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
"- 2tb - 2*top + 2*bottom\n"
"- 8 - 2*left + 2*right +2*top + 2*bottom")
)
- grid0.addWidget(gaps_label, 3, 0)
+ grid0.addWidget(gaps_label, 4, 0)
self.gaps_combo = FCComboBox()
- grid0.addWidget(self.gaps_combo, 3, 1)
+ grid0.addWidget(self.gaps_combo, 4, 1)
gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
for it in gaps_items:
@@ -5955,8 +5970,8 @@ class ToolsCutoutPrefGroupUI(OptionsGroupUI):
self.convex_box_label.setToolTip(
_("Create a convex shape surrounding the entire PCB.")
)
- grid0.addWidget(self.convex_box_label, 4, 0)
- grid0.addWidget(self.convex_box, 4, 1)
+ grid0.addWidget(self.convex_box_label, 5, 0)
+ grid0.addWidget(self.convex_box, 5, 1)
self.layout.addStretch()
diff --git a/flatcamTools/ToolCutOut.py b/flatcamTools/ToolCutOut.py
index b014e1bc..addf1d47 100644
--- a/flatcamTools/ToolCutOut.py
+++ b/flatcamTools/ToolCutOut.py
@@ -73,6 +73,20 @@ class CutOut(FlatCAMTool):
)
form_layout.addRow(self.object_label, self.obj_combo)
+ # Object kind
+ self.kindlabel = QtWidgets.QLabel(_('Obj kind:'))
+ self.kindlabel.setToolTip(
+ _("Choice of what kind the object we want to cutout is.
"
+ "- Single: contain a single PCB Gerber outline object.
"
+ "- Panel: a panel PCB Gerber object, which is made\n"
+ "out of many individual PCB outlines.")
+ )
+ self.obj_kind_combo = RadioSet([
+ {"label": _("Single"), "value": "single"},
+ {"label": _("Panel"), "value": "panel"},
+ ])
+ form_layout.addRow(self.kindlabel, self.obj_kind_combo)
+
# Tool Diameter
self.dia = FCEntry()
self.dia_label = QtWidgets.QLabel(_("Tool Dia:"))
@@ -320,6 +334,7 @@ class CutOut(FlatCAMTool):
self.reset_fields()
self.dia.set_value(float(self.app.defaults["tools_cutouttooldia"]))
+ self.obj_kind_combo.set_value(self.app.defaults["tools_cutoutkind"])
self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
self.gaps.set_value(self.app.defaults["tools_gaps_ff"])
@@ -338,7 +353,8 @@ class CutOut(FlatCAMTool):
# Get source object.
try:
cutout_obj = self.app.collection.get_by_name(str(name))
- except:
+ except Exception as e:
+ log.debug("CutOut.on_freeform_cutout() --> %s" % str(e))
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
return "Could not retrieve object: %s" % name
@@ -361,6 +377,11 @@ class CutOut(FlatCAMTool):
self.app.inform.emit(_("[WARNING_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
+ try:
+ kind = self.obj_kind_combo.get_value()
+ except ValueError:
+ return
+
try:
margin = float(self.margin.get_value())
except ValueError:
@@ -415,71 +436,89 @@ class CutOut(FlatCAMTool):
else:
object_geo = cutout_obj.solid_geometry
- # try:
- # __ = iter(object_geo)
- # except TypeError:
- # object_geo = [object_geo]
+ def cutout_handler(geom):
+ # Get min and max data for each object as we just cut rectangles across X or Y
+ xmin, ymin, xmax, ymax = recursive_bounds(geom)
- object_geo = unary_union(object_geo)
+ px = 0.5 * (xmin + xmax) + margin
+ py = 0.5 * (ymin + ymax) + margin
+ lenx = (xmax - xmin) + (margin * 2)
+ leny = (ymax - ymin) + (margin * 2)
- # for geo in object_geo:
- if isinstance(cutout_obj, FlatCAMGerber):
- geo = object_geo.buffer(margin + abs(dia / 2))
- geo = geo.exterior
+ proc_geometry = []
+
+ if gaps == '8' or gaps == '2LR':
+ geom = self.subtract_poly_from_geo(geom,
+ xmin - gapsize, # botleft_x
+ py - gapsize + leny / 4, # botleft_y
+ xmax + gapsize, # topright_x
+ py + gapsize + leny / 4) # topright_y
+ geom = self.subtract_poly_from_geo(geom,
+ xmin - gapsize,
+ py - gapsize - leny / 4,
+ xmax + gapsize,
+ py + gapsize - leny / 4)
+
+ if gaps == '8' or gaps == '2TB':
+ geom = self.subtract_poly_from_geo(geom,
+ px - gapsize + lenx / 4,
+ ymin - gapsize,
+ px + gapsize + lenx / 4,
+ ymax + gapsize)
+ geom = self.subtract_poly_from_geo(geom,
+ px - gapsize - lenx / 4,
+ ymin - gapsize,
+ px + gapsize - lenx / 4,
+ ymax + gapsize)
+
+ if gaps == '4' or gaps == 'LR':
+ geom = self.subtract_poly_from_geo(geom,
+ xmin - gapsize,
+ py - gapsize,
+ xmax + gapsize,
+ py + gapsize)
+
+ if gaps == '4' or gaps == 'TB':
+ geom = self.subtract_poly_from_geo(geom,
+ px - gapsize,
+ ymin - gapsize,
+ px + gapsize,
+ ymax + gapsize)
+
+ try:
+ for g in geom:
+ proc_geometry.append(g)
+ except TypeError:
+ proc_geometry.append(geom)
+
+ return proc_geometry
+
+ if kind == 'single':
+ object_geo = unary_union(object_geo)
+
+ # for geo in object_geo:
+ if isinstance(cutout_obj, FlatCAMGerber):
+ if isinstance(object_geo, MultiPolygon):
+ x0, y0, x1, y1 = object_geo.bounds
+ object_geo = box(x0, y0, x1, y1)
+
+ geo_buf = object_geo.buffer(margin + abs(dia / 2))
+ geo = geo_buf.exterior
+ else:
+ geo = object_geo
+
+ solid_geo = cutout_handler(geom=geo)
else:
- geo = object_geo
+ try:
+ __ = iter(object_geo)
+ except TypeError:
+ object_geo = [object_geo]
- # Get min and max data for each object as we just cut rectangles across X or Y
- xmin, ymin, xmax, ymax = recursive_bounds(geo)
+ for geom_struct in object_geo:
+ if isinstance(cutout_obj, FlatCAMGerber):
+ geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior
- px = 0.5 * (xmin + xmax) + margin
- py = 0.5 * (ymin + ymax) + margin
- lenx = (xmax - xmin) + (margin * 2)
- leny = (ymax - ymin) + (margin * 2)
-
- if gaps == '8' or gaps == '2LR':
- geo = self.subtract_poly_from_geo(geo,
- xmin - gapsize, # botleft_x
- py - gapsize + leny / 4, # botleft_y
- xmax + gapsize, # topright_x
- py + gapsize + leny / 4) # topright_y
- geo = self.subtract_poly_from_geo(geo,
- xmin - gapsize,
- py - gapsize - leny / 4,
- xmax + gapsize,
- py + gapsize - leny / 4)
-
- if gaps == '8' or gaps == '2TB':
- geo = self.subtract_poly_from_geo(geo,
- px - gapsize + lenx / 4,
- ymin - gapsize,
- px + gapsize + lenx / 4,
- ymax + gapsize)
- geo = self.subtract_poly_from_geo(geo,
- px - gapsize - lenx / 4,
- ymin - gapsize,
- px + gapsize - lenx / 4,
- ymax + gapsize)
-
- if gaps == '4' or gaps == 'LR':
- geo = self.subtract_poly_from_geo(geo,
- xmin - gapsize,
- py - gapsize,
- xmax + gapsize,
- py + gapsize)
-
- if gaps == '4' or gaps == 'TB':
- geo = self.subtract_poly_from_geo(geo,
- px - gapsize,
- ymin - gapsize,
- px + gapsize,
- ymax + gapsize)
-
- try:
- for g in geo:
- solid_geo.append(g)
- except TypeError:
- solid_geo.append(geo)
+ solid_geo += cutout_handler(geom=geom_struct)
geo_obj.solid_geometry = deepcopy(solid_geo)
xmin, ymin, xmax, ymax = recursive_bounds(geo_obj.solid_geometry)
@@ -508,7 +547,8 @@ class CutOut(FlatCAMTool):
# Get source object.
try:
cutout_obj = self.app.collection.get_by_name(str(name))
- except:
+ except Exception as e:
+ log.debug("CutOut.on_rectangular_cutout() --> %s" % str(e))
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
return "Could not retrieve object: %s" % name
@@ -530,6 +570,11 @@ class CutOut(FlatCAMTool):
self.app.inform.emit(_("[ERROR_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
+ try:
+ kind = self.obj_kind_combo.get_value()
+ except ValueError:
+ return
+
try:
margin = float(self.margin.get_value())
except ValueError:
@@ -577,68 +622,85 @@ class CutOut(FlatCAMTool):
solid_geo = []
object_geo = cutout_obj.solid_geometry
- try:
- __ = iter(object_geo)
- except TypeError:
- object_geo = [object_geo]
+ def cutout_rect_handler(geom):
+ proc_geometry = []
- object_geo = unary_union(object_geo)
+ px = 0.5 * (xmin + xmax) + margin
+ py = 0.5 * (ymin + ymax) + margin
+ lenx = (xmax - xmin) + (margin * 2)
+ leny = (ymax - ymin) + (margin * 2)
- xmin, ymin, xmax, ymax = object_geo.bounds
- geo = box(xmin, ymin, xmax, ymax)
+ if gaps == '8' or gaps == '2LR':
+ geom = self.subtract_poly_from_geo(geom,
+ xmin - gapsize, # botleft_x
+ py - gapsize + leny / 4, # botleft_y
+ xmax + gapsize, # topright_x
+ py + gapsize + leny / 4) # topright_y
+ geom = self.subtract_poly_from_geo(geom,
+ xmin - gapsize,
+ py - gapsize - leny / 4,
+ xmax + gapsize,
+ py + gapsize - leny / 4)
- # if Gerber create a buffer at a distance
- # if Geometry then cut through the geometry
- if isinstance(cutout_obj, FlatCAMGerber):
- geo = geo.buffer(margin + abs(dia / 2))
+ if gaps == '8' or gaps == '2TB':
+ geom = self.subtract_poly_from_geo(geom,
+ px - gapsize + lenx / 4,
+ ymin - gapsize,
+ px + gapsize + lenx / 4,
+ ymax + gapsize)
+ geom = self.subtract_poly_from_geo(geom,
+ px - gapsize - lenx / 4,
+ ymin - gapsize,
+ px + gapsize - lenx / 4,
+ ymax + gapsize)
- px = 0.5 * (xmin + xmax) + margin
- py = 0.5 * (ymin + ymax) + margin
- lenx = (xmax - xmin) + (margin * 2)
- leny = (ymax - ymin) + (margin * 2)
+ if gaps == '4' or gaps == 'LR':
+ geom = self.subtract_poly_from_geo(geom,
+ xmin - gapsize,
+ py - gapsize,
+ xmax + gapsize,
+ py + gapsize)
- if gaps == '8' or gaps == '2LR':
- geo = self.subtract_poly_from_geo(geo,
- xmin - gapsize, # botleft_x
- py - gapsize + leny / 4, # botleft_y
- xmax + gapsize, # topright_x
- py + gapsize + leny / 4) # topright_y
- geo = self.subtract_poly_from_geo(geo,
- xmin - gapsize,
- py - gapsize - leny / 4,
- xmax + gapsize,
- py + gapsize - leny / 4)
+ if gaps == '4' or gaps == 'TB':
+ geom = self.subtract_poly_from_geo(geom,
+ px - gapsize,
+ ymin - gapsize,
+ px + gapsize,
+ ymax + gapsize)
+ try:
+ for g in geom:
+ proc_geometry.append(g)
+ except TypeError:
+ proc_geometry.append(geom)
+ return proc_geometry
- if gaps == '8' or gaps == '2TB':
- geo = self.subtract_poly_from_geo(geo,
- px - gapsize + lenx / 4,
- ymin - gapsize,
- px + gapsize + lenx / 4,
- ymax + gapsize)
- geo = self.subtract_poly_from_geo(geo,
- px - gapsize - lenx / 4,
- ymin - gapsize,
- px + gapsize - lenx / 4,
- ymax + gapsize)
+ if kind == 'single':
+ object_geo = unary_union(object_geo)
- if gaps == '4' or gaps == 'LR':
- geo = self.subtract_poly_from_geo(geo,
- xmin - gapsize,
- py - gapsize,
- xmax + gapsize,
- py + gapsize)
+ xmin, ymin, xmax, ymax = object_geo.bounds
+ geo = box(xmin, ymin, xmax, ymax)
- if gaps == '4' or gaps == 'TB':
- geo = self.subtract_poly_from_geo(geo,
- px - gapsize,
- ymin - gapsize,
- px + gapsize,
- ymax + gapsize)
- try:
- for g in geo:
- solid_geo.append(g)
- except TypeError:
- solid_geo.append(geo)
+ # if Gerber create a buffer at a distance
+ # if Geometry then cut through the geometry
+ if isinstance(cutout_obj, FlatCAMGerber):
+ geo = geo.buffer(margin + abs(dia / 2))
+
+ solid_geo = cutout_rect_handler(geom=geo)
+ else:
+ try:
+ __ = iter(object_geo)
+ except TypeError:
+ object_geo = [object_geo]
+
+ for geom_struct in object_geo:
+ geom_struct = unary_union(geom_struct)
+ xmin, ymin, xmax, ymax = geom_struct.bounds
+ geom_struct = box(xmin, ymin, xmax, ymax)
+
+ if isinstance(cutout_obj, FlatCAMGerber):
+ geom_struct = geom_struct.buffer(margin + abs(dia / 2))
+
+ solid_geo += cutout_rect_handler(geom=geom_struct)
geo_obj.solid_geometry = deepcopy(solid_geo)
geo_obj.options['cnctooldia'] = str(dia)
@@ -715,7 +777,8 @@ class CutOut(FlatCAMTool):
# Get source object.
try:
cutout_obj = self.app.collection.get_by_name(str(name))
- except:
+ except Exception as e:
+ log.debug("CutOut.on_manual_cutout() --> %s" % str(e))
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Geometry object: %s") % name)
return "Could not retrieve object: %s" % name
@@ -746,18 +809,19 @@ class CutOut(FlatCAMTool):
# Get source object.
try:
cutout_obj = self.app.collection.get_by_name(str(name))
- except:
+ except Exception as e:
+ log.debug("CutOut.on_manual_geo() --> %s" % str(e))
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Gerber object: %s") % name)
return "Could not retrieve object: %s" % name
if cutout_obj is None:
self.app.inform.emit(_("[ERROR_NOTCL] There is no Gerber object selected for Cutout.\n"
- "Select one and try again."))
+ "Select one and try again."))
return
if not isinstance(cutout_obj, FlatCAMGerber):
self.app.inform.emit(_("[ERROR_NOTCL] The selected object has to be of Gerber type.\n"
- "Select a Gerber file and try again."))
+ "Select a Gerber file and try again."))
return
try:
@@ -768,13 +832,18 @@ class CutOut(FlatCAMTool):
dia = float(self.dia.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
- "Add it and retry."))
+ "Add it and retry."))
return
if 0 in {dia}:
self.app.inform.emit(_("[ERROR_NOTCL] Tool Diameter is zero value. Change it to a positive real number."))
return "Tool Diameter is zero value. Change it to a positive real number."
+ try:
+ kind = self.obj_kind_combo.get_value()
+ except ValueError:
+ return
+
try:
margin = float(self.margin.get_value())
except ValueError:
@@ -783,7 +852,7 @@ class CutOut(FlatCAMTool):
margin = float(self.margin.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit(_("[WARNING_NOTCL] Margin value is missing or wrong format. "
- "Add it and retry."))
+ "Add it and retry."))
return
convex_box = self.convex_box.get_value()
@@ -794,6 +863,10 @@ class CutOut(FlatCAMTool):
if convex_box:
geo = geo_union.convex_hull
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
+ elif kind == 'single':
+ x0, y0, x1, y1 = geo_union.bounds
+ geo = box(x0, y0, x1, y1)
+ geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
else:
geo = geo_union
geo = geo.buffer(margin + abs(dia / 2))