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))