2019-01-03 19:25:08 +00:00
|
|
|
from FlatCAMTool import FlatCAMTool
|
|
|
|
from ObjectCollection import *
|
|
|
|
from FlatCAMApp import *
|
2019-03-04 14:09:41 +00:00
|
|
|
from shapely.geometry import box
|
2019-07-09 10:58:33 +00:00
|
|
|
from shapely.ops import cascaded_union, unary_union
|
2019-03-07 16:04:11 +00:00
|
|
|
|
2019-03-07 23:32:18 +00:00
|
|
|
import gettext
|
|
|
|
import FlatCAMTranslation as fcTranslate
|
2019-05-17 00:04:28 +00:00
|
|
|
import builtins
|
2019-03-10 15:12:58 +00:00
|
|
|
|
2019-03-13 23:09:06 +00:00
|
|
|
fcTranslate.apply_language('strings')
|
2019-03-10 15:12:58 +00:00
|
|
|
if '_' not in builtins.__dict__:
|
|
|
|
_ = gettext.gettext
|
2019-03-07 16:04:11 +00:00
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-02-21 21:48:13 +00:00
|
|
|
class CutOut(FlatCAMTool):
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
toolName = _("Cutout PCB")
|
2019-03-04 14:09:41 +00:00
|
|
|
gapFinished = pyqtSignal()
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
def __init__(self, app):
|
|
|
|
FlatCAMTool.__init__(self, app)
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
self.app = app
|
|
|
|
self.canvas = app.plotcanvas
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Title
|
2019-02-18 01:45:34 +00:00
|
|
|
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
|
|
|
title_label.setStyleSheet("""
|
|
|
|
QLabel
|
|
|
|
{
|
|
|
|
font-size: 16px;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
""")
|
2019-01-03 19:25:08 +00:00
|
|
|
self.layout.addWidget(title_label)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Form Layout
|
2019-01-03 19:25:08 +00:00
|
|
|
form_layout = QtWidgets.QFormLayout()
|
|
|
|
self.layout.addLayout(form_layout)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Type of object to be cutout
|
2019-01-03 19:25:08 +00:00
|
|
|
self.type_obj_combo = QtWidgets.QComboBox()
|
|
|
|
self.type_obj_combo.addItem("Gerber")
|
|
|
|
self.type_obj_combo.addItem("Excellon")
|
|
|
|
self.type_obj_combo.addItem("Geometry")
|
|
|
|
|
|
|
|
# we get rid of item1 ("Excellon") as it is not suitable for creating film
|
|
|
|
self.type_obj_combo.view().setRowHidden(1, True)
|
|
|
|
self.type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
|
|
|
|
# self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
|
|
|
|
self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
|
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
self.type_obj_combo_label = QtWidgets.QLabel(_("Obj Type:"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.type_obj_combo_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Specify the type of object to be cutout.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"It can be of type: Gerber or Geometry.\n"
|
|
|
|
"What is selected here will dictate the kind\n"
|
|
|
|
"of objects that will populate the 'Object' combobox.")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
2019-03-04 14:09:41 +00:00
|
|
|
self.type_obj_combo_label.setFixedWidth(60)
|
2019-01-03 19:25:08 +00:00
|
|
|
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Object to be cutout
|
2019-01-03 19:25:08 +00:00
|
|
|
self.obj_combo = QtWidgets.QComboBox()
|
|
|
|
self.obj_combo.setModel(self.app.collection)
|
|
|
|
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
2019-02-04 18:49:37 +00:00
|
|
|
self.obj_combo.setCurrentIndex(1)
|
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
self.object_label = QtWidgets.QLabel(_("Object:"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.object_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Object to be cutout. ")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
|
|
|
form_layout.addRow(self.object_label, self.obj_combo)
|
|
|
|
|
|
|
|
# Tool Diameter
|
|
|
|
self.dia = FCEntry()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.dia_label = QtWidgets.QLabel(_("Tool Dia:"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.dia_label.setToolTip(
|
2019-05-17 00:04:28 +00:00
|
|
|
_("Diameter of the tool used to cutout\n"
|
|
|
|
"the PCB shape out of the surrounding material.")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout.addRow(self.dia_label, self.dia)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
# Margin
|
|
|
|
self.margin = FCEntry()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.margin_label = QtWidgets.QLabel(_("Margin:"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.margin_label.setToolTip(
|
2019-05-17 00:04:28 +00:00
|
|
|
_("Margin over bounds. A positive value here\n"
|
|
|
|
"will make the cutout of the PCB further from\n"
|
|
|
|
"the actual PCB border")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout.addRow(self.margin_label, self.margin)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
# Gapsize
|
|
|
|
self.gapsize = FCEntry()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.gapsize_label = QtWidgets.QLabel(_("Gap size:"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.gapsize_label.setToolTip(
|
2019-05-17 00:04:28 +00:00
|
|
|
_("The size of the bridge gaps in the cutout\n"
|
|
|
|
"used to keep the board connected to\n"
|
|
|
|
"the surrounding material (the one \n"
|
|
|
|
"from which the PCB is cutout).")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout.addRow(self.gapsize_label, self.gapsize)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
# How gaps wil be rendered:
|
|
|
|
# lr - left + right
|
|
|
|
# tb - top + bottom
|
|
|
|
# 4 - left + right +top + bottom
|
|
|
|
# 2lr - 2*left + 2*right
|
|
|
|
# 2tb - 2*top + 2*bottom
|
|
|
|
# 8 - 2*left + 2*right +2*top + 2*bottom
|
|
|
|
|
2019-03-31 18:52:27 +00:00
|
|
|
# Surrounding convex box shape
|
|
|
|
self.convex_box = FCCheckBox()
|
|
|
|
self.convex_box_label = QtWidgets.QLabel(_("Convex Sh.:"))
|
|
|
|
self.convex_box_label.setToolTip(
|
2019-05-17 14:17:58 +00:00
|
|
|
_("Create a convex shape surrounding the entire PCB.\n"
|
|
|
|
"Used only if the source object type is Gerber.")
|
2019-03-31 18:52:27 +00:00
|
|
|
)
|
|
|
|
form_layout.addRow(self.convex_box_label, self.convex_box)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Title2
|
2019-03-10 12:34:13 +00:00
|
|
|
title_param_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('A. Automatic Bridge Gaps'))
|
2019-03-04 14:09:41 +00:00
|
|
|
title_param_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("This section handle creation of automatic bridge gaps.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
self.layout.addWidget(title_param_label)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Form Layout
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout_2 = QtWidgets.QFormLayout()
|
|
|
|
self.layout.addLayout(form_layout_2)
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
# Gaps
|
2019-03-10 12:34:13 +00:00
|
|
|
gaps_label = QtWidgets.QLabel(_('Gaps:'))
|
2019-03-04 14:09:41 +00:00
|
|
|
gaps_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Number of gaps used for the Automatic cutout.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"There can be maximum 8 bridges/gaps.\n"
|
|
|
|
"The choices are:\n"
|
|
|
|
"- lr - left + right\n"
|
|
|
|
"- tb - top + bottom\n"
|
|
|
|
"- 4 - left + right +top + bottom\n"
|
|
|
|
"- 2lr - 2*left + 2*right\n"
|
|
|
|
"- 2tb - 2*top + 2*bottom\n"
|
|
|
|
"- 8 - 2*left + 2*right +2*top + 2*bottom")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
2019-03-04 14:09:41 +00:00
|
|
|
gaps_label.setFixedWidth(60)
|
2019-01-29 19:47:27 +00:00
|
|
|
|
|
|
|
self.gaps = FCComboBox()
|
|
|
|
gaps_items = ['LR', 'TB', '4', '2LR', '2TB', '8']
|
|
|
|
for it in gaps_items:
|
|
|
|
self.gaps.addItem(it)
|
|
|
|
self.gaps.setStyleSheet('background-color: rgb(255,255,255)')
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout_2.addRow(gaps_label, self.gaps)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Buttons
|
2019-01-03 19:25:08 +00:00
|
|
|
hlay = QtWidgets.QHBoxLayout()
|
|
|
|
self.layout.addLayout(hlay)
|
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
title_ff_label = QtWidgets.QLabel("<b>%s</b>" % _('FreeForm:'))
|
2019-03-04 14:09:41 +00:00
|
|
|
title_ff_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("The cutout shape can be of ny shape.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"Useful when the PCB has a non-rectangular shape.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
hlay.addWidget(title_ff_label)
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
hlay.addStretch()
|
2019-03-04 14:09:41 +00:00
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
self.ff_cutout_object_btn = QtWidgets.QPushButton(_("Generate Geo"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.ff_cutout_object_btn.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Cutout the selected object.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"The cutout shape can be of any shape.\n"
|
|
|
|
"Useful when the PCB has a non-rectangular shape.")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
|
|
|
hlay.addWidget(self.ff_cutout_object_btn)
|
|
|
|
|
|
|
|
hlay2 = QtWidgets.QHBoxLayout()
|
|
|
|
self.layout.addLayout(hlay2)
|
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
title_rct_label = QtWidgets.QLabel("<b>%s</b>" % _('Rectangular:'))
|
2019-03-04 14:09:41 +00:00
|
|
|
title_rct_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("The resulting cutout shape is\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"always a rectangle shape and it will be\n"
|
|
|
|
"the bounding box of the Object.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
hlay2.addWidget(title_rct_label)
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
hlay2.addStretch()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.rect_cutout_object_btn = QtWidgets.QPushButton(_("Generate Geo"))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.rect_cutout_object_btn.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Cutout the selected object.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"The resulting cutout shape is\n"
|
|
|
|
"always a rectangle shape and it will be\n"
|
|
|
|
"the bounding box of the Object.")
|
2019-01-03 19:25:08 +00:00
|
|
|
)
|
|
|
|
hlay2.addWidget(self.rect_cutout_object_btn)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Title5
|
2019-03-10 12:34:13 +00:00
|
|
|
title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
|
2019-03-04 14:09:41 +00:00
|
|
|
title_manual_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("This section handle creation of manual bridge gaps.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"This is done by mouse clicking on the perimeter of the\n"
|
|
|
|
"Geometry object that is used as a cutout object. ")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
2019-03-04 03:10:58 +00:00
|
|
|
self.layout.addWidget(title_manual_label)
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Form Layout
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout_3 = QtWidgets.QFormLayout()
|
|
|
|
self.layout.addLayout(form_layout_3)
|
2019-03-04 03:10:58 +00:00
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Manual Geo Object
|
2019-03-04 14:09:41 +00:00
|
|
|
self.man_object_combo = QtWidgets.QComboBox()
|
|
|
|
self.man_object_combo.setModel(self.app.collection)
|
|
|
|
self.man_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
|
|
|
|
self.man_object_combo.setCurrentIndex(1)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
self.man_object_label = QtWidgets.QLabel(_("Geo Obj:"))
|
2019-03-04 14:09:41 +00:00
|
|
|
self.man_object_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Geometry object used to create the manual cutout.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
self.man_object_label.setFixedWidth(60)
|
|
|
|
# e_lab_0 = QtWidgets.QLabel('')
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
form_layout_3.addRow(self.man_object_label, self.man_object_combo)
|
|
|
|
# form_layout_3.addRow(e_lab_0)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
hlay3 = QtWidgets.QHBoxLayout()
|
|
|
|
self.layout.addLayout(hlay3)
|
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
self.man_geo_label = QtWidgets.QLabel(_("Manual Geo:"))
|
2019-03-04 14:09:41 +00:00
|
|
|
self.man_geo_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("If the object to be cutout is a Gerber\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"first create a Geometry that surrounds it,\n"
|
|
|
|
"to be used as the cutout, if one doesn't exist yet.\n"
|
|
|
|
"Select the source Gerber file in the top object combobox.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
hlay3.addWidget(self.man_geo_label)
|
|
|
|
|
|
|
|
hlay3.addStretch()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Geo"))
|
2019-03-04 14:09:41 +00:00
|
|
|
self.man_geo_creation_btn.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("If the object to be cutout is a Gerber\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"first create a Geometry that surrounds it,\n"
|
|
|
|
"to be used as the cutout, if one doesn't exist yet.\n"
|
|
|
|
"Select the source Gerber file in the top object combobox.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
hlay3.addWidget(self.man_geo_creation_btn)
|
|
|
|
|
|
|
|
hlay4 = QtWidgets.QHBoxLayout()
|
|
|
|
self.layout.addLayout(hlay4)
|
|
|
|
|
2019-03-10 12:34:13 +00:00
|
|
|
self.man_bridge_gaps_label = QtWidgets.QLabel(_("Manual Add Bridge Gaps:"))
|
2019-03-04 14:09:41 +00:00
|
|
|
self.man_bridge_gaps_label.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Use the left mouse button (LMB) click\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"to create a bridge gap to separate the PCB from\n"
|
|
|
|
"the surrounding material.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
hlay4.addWidget(self.man_bridge_gaps_label)
|
|
|
|
|
|
|
|
hlay4.addStretch()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Generate Gap"))
|
2019-03-04 14:09:41 +00:00
|
|
|
self.man_gaps_creation_btn.setToolTip(
|
2019-03-10 12:34:13 +00:00
|
|
|
_("Use the left mouse button (LMB) click\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"to create a bridge gap to separate the PCB from\n"
|
|
|
|
"the surrounding material.\n"
|
|
|
|
"The LMB click has to be done on the perimeter of\n"
|
|
|
|
"the Geometry object used as a cutout geometry.")
|
2019-03-04 14:09:41 +00:00
|
|
|
)
|
|
|
|
hlay4.addWidget(self.man_gaps_creation_btn)
|
|
|
|
|
|
|
|
self.layout.addStretch()
|
|
|
|
|
|
|
|
self.cutting_gapsize = 0.0
|
|
|
|
self.cutting_dia = 0.0
|
|
|
|
|
|
|
|
# true if we want to repeat the gap without clicking again on the button
|
|
|
|
self.repeat_gap = False
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-05-12 22:57:37 +00:00
|
|
|
self.flat_geometry = []
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
# Signals
|
2019-03-10 15:12:58 +00:00
|
|
|
self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
|
|
|
|
self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
|
|
|
|
|
|
|
|
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
|
|
|
self.man_geo_creation_btn.clicked.connect(self.on_manual_geo)
|
|
|
|
self.man_gaps_creation_btn.clicked.connect(self.on_manual_gap_click)
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
def on_type_obj_index_changed(self, index):
|
|
|
|
obj_type = self.type_obj_combo.currentIndex()
|
|
|
|
self.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
|
|
|
self.obj_combo.setCurrentIndex(0)
|
|
|
|
|
2019-03-10 15:12:58 +00:00
|
|
|
def run(self, toggle=True):
|
2019-02-03 21:08:09 +00:00
|
|
|
self.app.report_usage("ToolCutOut()")
|
|
|
|
|
2019-03-11 20:58:27 +00:00
|
|
|
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:
|
2019-03-11 21:04:38 +00:00
|
|
|
try:
|
|
|
|
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
|
|
|
self.app.ui.splitter.setSizes([0, 1])
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2019-03-13 23:09:06 +00:00
|
|
|
else:
|
|
|
|
if self.app.ui.splitter.sizes()[0] == 0:
|
|
|
|
self.app.ui.splitter.setSizes([1, 1])
|
2019-03-11 20:58:27 +00:00
|
|
|
|
|
|
|
FlatCAMTool.run(self)
|
2019-02-27 15:32:52 +00:00
|
|
|
self.set_tool_ui()
|
2019-02-12 02:00:11 +00:00
|
|
|
|
2019-06-09 18:09:15 +00:00
|
|
|
self.app.ui.notebook.setTabText(2, _("Cutout Tool"))
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-01-27 01:32:09 +00:00
|
|
|
def install(self, icon=None, separator=None, **kwargs):
|
|
|
|
FlatCAMTool.install(self, icon, separator, shortcut='ALT+U', **kwargs)
|
|
|
|
|
2019-02-02 16:26:01 +00:00
|
|
|
def set_tool_ui(self):
|
|
|
|
self.reset_fields()
|
|
|
|
|
2019-01-27 13:46:54 +00:00
|
|
|
self.dia.set_value(float(self.app.defaults["tools_cutouttooldia"]))
|
|
|
|
self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
|
|
|
|
self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
|
2019-03-31 18:52:27 +00:00
|
|
|
self.gaps.set_value(self.app.defaults["tools_gaps_ff"])
|
|
|
|
self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
self.gapFinished.connect(self.on_gap_finished)
|
2019-01-27 13:46:54 +00:00
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
def on_freeform_cutout(self):
|
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
# def subtract_rectangle(obj_, x0, y0, x1, y1):
|
|
|
|
# pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
|
|
|
# obj_.subtract_polygon(pts)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
name = self.obj_combo.currentText()
|
|
|
|
|
|
|
|
# Get source object.
|
|
|
|
try:
|
|
|
|
cutout_obj = self.app.collection.get_by_name(str(name))
|
|
|
|
except:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
|
2019-01-03 19:25:08 +00:00
|
|
|
return "Could not retrieve object: %s" % name
|
|
|
|
|
|
|
|
if cutout_obj is None:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] There is no object selected for Cutout.\nSelect one and try again."))
|
2019-01-29 19:47:27 +00:00
|
|
|
return
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
dia = float(self.dia.get_value())
|
2019-02-02 21:56:08 +00:00
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
dia = float(self.dia.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-02-02 21:56:08 +00:00
|
|
|
return
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
if 0 in {dia}:
|
2019-03-28 22:26:00 +00:00
|
|
|
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."
|
2019-03-04 14:09:41 +00:00
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
try:
|
|
|
|
margin = float(self.margin.get_value())
|
2019-02-02 21:56:08 +00:00
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
margin = float(self.margin.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Margin value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-02-02 21:56:08 +00:00
|
|
|
return
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
try:
|
|
|
|
gapsize = float(self.gapsize.get_value())
|
2019-02-02 21:56:08 +00:00
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
gapsize = float(self.gapsize.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Gap size value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-02-02 21:56:08 +00:00
|
|
|
return
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
try:
|
|
|
|
gaps = self.gaps.get_value()
|
|
|
|
except TypeError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry."))
|
2019-01-03 19:25:08 +00:00
|
|
|
return
|
|
|
|
|
2019-02-16 10:40:44 +00:00
|
|
|
if gaps not in ['LR', 'TB', '2LR', '2TB', '4', '8']:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Fill in a correct value and retry. "))
|
2019-01-03 19:25:08 +00:00
|
|
|
return
|
|
|
|
|
2019-01-29 20:03:24 +00:00
|
|
|
if cutout_obj.multigeo is True:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
|
2019-05-17 00:04:28 +00:00
|
|
|
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
|
|
|
|
"and after that perform Cutout."))
|
2019-01-29 20:03:24 +00:00
|
|
|
return
|
|
|
|
|
2019-03-31 18:52:27 +00:00
|
|
|
convex_box = self.convex_box.get_value()
|
|
|
|
|
2019-01-29 19:47:27 +00:00
|
|
|
gapsize = gapsize / 2 + (dia / 2)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
def geo_init(geo_obj, app_obj):
|
|
|
|
solid_geo = []
|
|
|
|
|
|
|
|
if isinstance(cutout_obj, FlatCAMGerber):
|
2019-03-31 18:52:27 +00:00
|
|
|
if convex_box:
|
2019-05-17 14:17:58 +00:00
|
|
|
object_geo = cutout_obj.solid_geometry.convex_hull
|
2019-03-31 18:52:27 +00:00
|
|
|
else:
|
2019-05-17 14:17:58 +00:00
|
|
|
object_geo = cutout_obj.solid_geometry
|
|
|
|
else:
|
|
|
|
object_geo = cutout_obj.solid_geometry
|
2019-05-12 22:57:37 +00:00
|
|
|
|
2019-07-05 17:45:15 +00:00
|
|
|
# try:
|
|
|
|
# __ = iter(object_geo)
|
|
|
|
# except TypeError:
|
|
|
|
# object_geo = [object_geo]
|
|
|
|
|
|
|
|
object_geo = unary_union(object_geo)
|
|
|
|
|
2019-07-05 09:13:53 +00:00
|
|
|
# for geo in object_geo:
|
|
|
|
if isinstance(cutout_obj, FlatCAMGerber):
|
2019-07-08 19:42:58 +00:00
|
|
|
geo = object_geo.buffer(margin + abs(dia / 2))
|
|
|
|
geo = geo.exterior
|
2019-07-05 09:13:53 +00:00
|
|
|
else:
|
|
|
|
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)
|
|
|
|
|
|
|
|
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,
|
2019-07-08 19:42:58 +00:00
|
|
|
xmin - gapsize, # botleft_x
|
2019-07-05 09:13:53 +00:00
|
|
|
py - gapsize + leny / 4, # botleft_y
|
2019-07-08 19:42:58 +00:00
|
|
|
xmax + gapsize, # topright_x
|
2019-07-05 09:13:53 +00:00
|
|
|
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)
|
2019-05-17 14:17:58 +00:00
|
|
|
|
2019-07-05 09:13:53 +00:00
|
|
|
try:
|
|
|
|
for g in geo:
|
|
|
|
solid_geo.append(g)
|
|
|
|
except TypeError:
|
|
|
|
solid_geo.append(geo)
|
2019-05-17 14:17:58 +00:00
|
|
|
|
|
|
|
geo_obj.solid_geometry = deepcopy(solid_geo)
|
2019-06-07 20:14:00 +00:00
|
|
|
xmin, ymin, xmax, ymax = recursive_bounds(geo_obj.solid_geometry)
|
|
|
|
geo_obj.options['xmin'] = xmin
|
|
|
|
geo_obj.options['ymin'] = ymin
|
|
|
|
geo_obj.options['xmax'] = xmax
|
|
|
|
geo_obj.options['ymax'] = ymax
|
2019-07-08 17:06:41 +00:00
|
|
|
geo_obj.options['cnctooldia'] = str(dia)
|
2019-05-17 14:17:58 +00:00
|
|
|
|
|
|
|
outname = cutout_obj.options["name"] + "_cutout"
|
|
|
|
self.app.new_object('geometry', outname, geo_init)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
cutout_obj.plot()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[success] Any form CutOut operation finished."))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
|
2019-02-15 22:42:18 +00:00
|
|
|
self.app.should_we_save = True
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
def on_rectangular_cutout(self):
|
2019-03-04 14:09:41 +00:00
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
# def subtract_rectangle(obj_, x0, y0, x1, y1):
|
|
|
|
# pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
|
|
|
# obj_.subtract_polygon(pts)
|
2019-03-04 14:09:41 +00:00
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
name = self.obj_combo.currentText()
|
|
|
|
|
|
|
|
# Get source object.
|
|
|
|
try:
|
|
|
|
cutout_obj = self.app.collection.get_by_name(str(name))
|
|
|
|
except:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % name)
|
2019-01-03 19:25:08 +00:00
|
|
|
return "Could not retrieve object: %s" % name
|
|
|
|
|
|
|
|
if cutout_obj is None:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % cutout_obj)
|
2019-01-03 19:25:08 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
dia = float(self.dia.get_value())
|
2019-02-02 21:56:08 +00:00
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
dia = float(self.dia.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-02-02 21:56:08 +00:00
|
|
|
return
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
if 0 in {dia}:
|
2019-03-28 22:26:00 +00:00
|
|
|
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."
|
2019-03-04 14:09:41 +00:00
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
try:
|
|
|
|
margin = float(self.margin.get_value())
|
2019-02-02 21:56:08 +00:00
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
margin = float(self.margin.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Margin value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-02-02 21:56:08 +00:00
|
|
|
return
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
try:
|
|
|
|
gapsize = float(self.gapsize.get_value())
|
2019-02-02 21:56:08 +00:00
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
gapsize = float(self.gapsize.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Gap size value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-02-02 21:56:08 +00:00
|
|
|
return
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
try:
|
2019-03-04 14:09:41 +00:00
|
|
|
gaps = self.gaps.get_value()
|
2019-01-03 19:25:08 +00:00
|
|
|
except TypeError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry."))
|
2019-01-03 19:25:08 +00:00
|
|
|
return
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
if gaps not in ['LR', 'TB', '2LR', '2TB', '4', '8']:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Fill in a correct value and retry. "))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-01-29 20:03:24 +00:00
|
|
|
if cutout_obj.multigeo is True:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
|
2019-01-29 20:03:24 +00:00
|
|
|
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
|
2019-03-07 15:37:38 +00:00
|
|
|
"and after that perform Cutout."))
|
2019-01-29 20:03:24 +00:00
|
|
|
return
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
# Get min and max data for each object as we just cut rectangles across X or Y
|
|
|
|
|
|
|
|
gapsize = gapsize / 2 + (dia / 2)
|
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
def geo_init(geo_obj, app_obj):
|
2019-05-17 14:17:58 +00:00
|
|
|
solid_geo = []
|
2019-05-17 17:11:01 +00:00
|
|
|
object_geo = cutout_obj.solid_geometry
|
|
|
|
|
|
|
|
try:
|
2019-06-03 01:47:29 +00:00
|
|
|
__ = iter(object_geo)
|
2019-05-17 17:11:01 +00:00
|
|
|
except TypeError:
|
|
|
|
object_geo = [object_geo]
|
|
|
|
|
2019-07-05 09:13:53 +00:00
|
|
|
object_geo = unary_union(object_geo)
|
|
|
|
|
|
|
|
xmin, ymin, xmax, ymax = object_geo.bounds
|
|
|
|
geo = box(xmin, ymin, xmax, ymax)
|
|
|
|
|
|
|
|
# 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))
|
|
|
|
|
|
|
|
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)
|
2019-05-17 14:17:58 +00:00
|
|
|
|
|
|
|
geo_obj.solid_geometry = deepcopy(solid_geo)
|
2019-07-08 17:06:41 +00:00
|
|
|
geo_obj.options['cnctooldia'] = str(dia)
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
outname = cutout_obj.options["name"] + "_cutout"
|
|
|
|
self.app.new_object('geometry', outname, geo_init)
|
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
# cutout_obj.plot()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[success] Any form CutOut operation finished."))
|
2019-01-03 19:25:08 +00:00
|
|
|
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
|
2019-03-04 14:09:41 +00:00
|
|
|
self.app.should_we_save = True
|
|
|
|
|
|
|
|
def on_manual_gap_click(self):
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("Click on the selected geometry object perimeter to create a bridge gap ..."))
|
2019-03-04 14:09:41 +00:00
|
|
|
self.app.geo_editor.tool_shape.enabled = True
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.cutting_dia = float(self.dia.get_value())
|
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
self.cutting_dia = float(self.dia.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if 0 in {self.cutting_dia}:
|
2019-03-28 22:26:00 +00:00
|
|
|
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."
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.cutting_gapsize = float(self.gapsize.get_value())
|
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
self.cutting_gapsize = float(self.gapsize.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Gap size value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
|
|
|
|
self.app.plotcanvas.vis_connect('key_press', self.on_key_press)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_release', self.doit)
|
|
|
|
|
|
|
|
# To be called after clicking on the plot.
|
|
|
|
def doit(self, event):
|
|
|
|
# do paint single only for left mouse clicks
|
|
|
|
if event.button == 1:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("Making manual bridge gap..."))
|
2019-03-04 14:09:41 +00:00
|
|
|
pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
|
|
|
|
self.on_manual_cutout(click_pos=pos)
|
|
|
|
|
|
|
|
self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
|
|
|
|
self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
|
|
|
|
|
|
|
|
self.app.geo_editor.tool_shape.clear(update=True)
|
|
|
|
self.app.geo_editor.tool_shape.enabled = False
|
|
|
|
self.gapFinished.emit()
|
|
|
|
|
|
|
|
def on_manual_cutout(self, click_pos):
|
|
|
|
name = self.man_object_combo.currentText()
|
|
|
|
|
|
|
|
# Get source object.
|
|
|
|
try:
|
|
|
|
cutout_obj = self.app.collection.get_by_name(str(name))
|
|
|
|
except:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Geometry object: %s") % name)
|
2019-03-04 14:09:41 +00:00
|
|
|
return "Could not retrieve object: %s" % name
|
|
|
|
|
|
|
|
if cutout_obj is None:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] Geometry object for manual cutout not found: %s") % cutout_obj)
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# use the snapped position as reference
|
|
|
|
snapped_pos = self.app.geo_editor.snap(click_pos[0], click_pos[1])
|
|
|
|
|
|
|
|
cut_poly = self.cutting_geo(pos=(snapped_pos[0], snapped_pos[1]))
|
|
|
|
cutout_obj.subtract_polygon(cut_poly)
|
|
|
|
|
|
|
|
cutout_obj.plot()
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[success] Added manual Bridge Gap."))
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
self.app.should_we_save = True
|
|
|
|
|
|
|
|
def on_gap_finished(self):
|
|
|
|
# if CTRL key modifier is pressed then repeat the bridge gap cut
|
|
|
|
key_modifier = QtWidgets.QApplication.keyboardModifiers()
|
|
|
|
if key_modifier == Qt.ControlModifier:
|
|
|
|
self.on_manual_gap_click()
|
|
|
|
|
|
|
|
def on_manual_geo(self):
|
|
|
|
name = self.obj_combo.currentText()
|
|
|
|
|
|
|
|
# Get source object.
|
|
|
|
try:
|
|
|
|
cutout_obj = self.app.collection.get_by_name(str(name))
|
|
|
|
except:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve Gerber object: %s") % name)
|
2019-03-04 14:09:41 +00:00
|
|
|
return "Could not retrieve object: %s" % name
|
|
|
|
|
|
|
|
if cutout_obj is None:
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] There is no Gerber object selected for Cutout.\n"
|
2019-03-07 15:37:38 +00:00
|
|
|
"Select one and try again."))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if not isinstance(cutout_obj, FlatCAMGerber):
|
2019-03-28 22:26:00 +00:00
|
|
|
self.app.inform.emit(_("[ERROR_NOTCL] The selected object has to be of Gerber type.\n"
|
2019-03-07 15:37:38 +00:00
|
|
|
"Select a Gerber file and try again."))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
dia = float(self.dia.get_value())
|
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
dia = float(self.dia.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if 0 in {dia}:
|
2019-03-28 22:26:00 +00:00
|
|
|
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."
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
margin = float(self.margin.get_value())
|
|
|
|
except ValueError:
|
|
|
|
# try to convert comma to decimal point. if it's still not working error message and return
|
|
|
|
try:
|
|
|
|
margin = float(self.margin.get_value().replace(',', '.'))
|
|
|
|
except ValueError:
|
2019-03-10 12:34:13 +00:00
|
|
|
self.app.inform.emit(_("[WARNING_NOTCL] Margin value is missing or wrong format. "
|
2019-03-07 15:37:38 +00:00
|
|
|
"Add it and retry."))
|
2019-03-04 14:09:41 +00:00
|
|
|
return
|
|
|
|
|
2019-03-31 18:52:27 +00:00
|
|
|
convex_box = self.convex_box.get_value()
|
|
|
|
|
2019-03-04 14:09:41 +00:00
|
|
|
def geo_init(geo_obj, app_obj):
|
2019-07-05 17:45:15 +00:00
|
|
|
geo_union = unary_union(cutout_obj.solid_geometry)
|
|
|
|
|
2019-03-31 18:52:27 +00:00
|
|
|
if convex_box:
|
2019-07-05 17:45:15 +00:00
|
|
|
geo = geo_union.convex_hull
|
2019-03-31 18:52:27 +00:00
|
|
|
geo_obj.solid_geometry = geo.buffer(margin + abs(dia / 2))
|
|
|
|
else:
|
2019-07-05 17:45:15 +00:00
|
|
|
geo = geo_union
|
2019-05-12 22:57:37 +00:00
|
|
|
geo = geo.buffer(margin + abs(dia / 2))
|
|
|
|
if isinstance(geo, Polygon):
|
|
|
|
geo_obj.solid_geometry = geo.exterior
|
|
|
|
elif isinstance(geo, MultiPolygon):
|
|
|
|
solid_geo = []
|
|
|
|
for poly in geo:
|
|
|
|
solid_geo.append(poly.exterior)
|
|
|
|
geo_obj.solid_geometry = deepcopy(solid_geo)
|
2019-07-08 17:06:41 +00:00
|
|
|
geo_obj.options['cnctooldia'] = str(dia)
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
outname = cutout_obj.options["name"] + "_cutout"
|
|
|
|
self.app.new_object('geometry', outname, geo_init)
|
|
|
|
|
|
|
|
def cutting_geo(self, pos):
|
2019-03-31 18:52:27 +00:00
|
|
|
offset = self.cutting_dia / 2 + self.cutting_gapsize / 2
|
2019-03-04 14:09:41 +00:00
|
|
|
|
|
|
|
# cutting area definition
|
|
|
|
orig_x = pos[0]
|
|
|
|
orig_y = pos[1]
|
|
|
|
xmin = orig_x - offset
|
|
|
|
ymin = orig_y - offset
|
|
|
|
xmax = orig_x + offset
|
|
|
|
ymax = orig_y + offset
|
|
|
|
|
|
|
|
cut_poly = box(xmin, ymin, xmax, ymax)
|
|
|
|
return cut_poly
|
|
|
|
|
|
|
|
def on_mouse_move(self, event):
|
|
|
|
|
|
|
|
self.app.on_mouse_move_over_plot(event=event)
|
|
|
|
|
|
|
|
pos = self.canvas.vispy_canvas.translate_coords(event.pos)
|
|
|
|
event.xdata, event.ydata = pos[0], pos[1]
|
|
|
|
|
|
|
|
try:
|
|
|
|
x = float(event.xdata)
|
|
|
|
y = float(event.ydata)
|
|
|
|
except TypeError:
|
|
|
|
return
|
|
|
|
|
|
|
|
snap_x, snap_y = self.app.geo_editor.snap(x, y)
|
|
|
|
|
|
|
|
geo = self.cutting_geo(pos=(snap_x, snap_y))
|
|
|
|
|
|
|
|
# Remove any previous utility shape
|
|
|
|
self.app.geo_editor.tool_shape.clear(update=True)
|
|
|
|
self.draw_utility_geometry(geo=geo)
|
|
|
|
|
|
|
|
def draw_utility_geometry(self, geo):
|
|
|
|
self.app.geo_editor.tool_shape.add(
|
|
|
|
shape=geo,
|
|
|
|
color=(self.app.defaults["global_draw_color"] + '80'),
|
|
|
|
update=False,
|
|
|
|
layer=0,
|
|
|
|
tolerance=None)
|
|
|
|
self.app.geo_editor.tool_shape.redraw()
|
|
|
|
|
|
|
|
def on_key_press(self, event):
|
|
|
|
# events out of the self.app.collection view (it's about Project Tab) are of type int
|
|
|
|
if type(event) is int:
|
|
|
|
key = event
|
|
|
|
# events from the GUI are of type QKeyEvent
|
|
|
|
elif type(event) == QtGui.QKeyEvent:
|
|
|
|
key = event.key()
|
|
|
|
# events from Vispy are of type KeyEvent
|
|
|
|
else:
|
|
|
|
key = event.key
|
|
|
|
|
|
|
|
# Escape = Deselect All
|
|
|
|
if key == QtCore.Qt.Key_Escape or key == 'Escape':
|
|
|
|
self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
|
|
|
|
self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
|
|
|
|
self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
|
|
|
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
|
|
|
|
|
|
|
|
# Remove any previous utility shape
|
|
|
|
self.app.geo_editor.tool_shape.clear(update=True)
|
|
|
|
self.app.geo_editor.tool_shape.enabled = False
|
2019-01-03 19:25:08 +00:00
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
|
2019-05-12 22:57:37 +00:00
|
|
|
"""
|
2019-05-17 14:17:58 +00:00
|
|
|
Subtract polygon made from points from the given object.
|
|
|
|
This only operates on the paths in the original geometry,
|
2019-05-12 22:57:37 +00:00
|
|
|
i.e. it converts polygons into paths.
|
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
:param x0: x coord for lower left vertice of the polygon.
|
|
|
|
:param y0: y coord for lower left vertice of the polygon.
|
|
|
|
:param x1: x coord for upper right vertice of the polygon.
|
|
|
|
:param y1: y coord for upper right vertice of the polygon.
|
|
|
|
|
2019-05-17 00:04:28 +00:00
|
|
|
:param solid_geo: Geometry from which to substract. If none, use the solid_geomety property of the object
|
2019-05-12 22:57:37 +00:00
|
|
|
:return: none
|
|
|
|
"""
|
2019-05-17 14:17:58 +00:00
|
|
|
points = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
2019-05-12 22:57:37 +00:00
|
|
|
|
|
|
|
# pathonly should be allways True, otherwise polygons are not subtracted
|
2019-05-17 14:17:58 +00:00
|
|
|
flat_geometry = flatten(geometry=solid_geo)
|
2019-05-12 22:57:37 +00:00
|
|
|
|
|
|
|
log.debug("%d paths" % len(flat_geometry))
|
2019-05-17 14:17:58 +00:00
|
|
|
|
2019-05-12 22:57:37 +00:00
|
|
|
polygon = Polygon(points)
|
|
|
|
toolgeo = cascaded_union(polygon)
|
|
|
|
diffs = []
|
|
|
|
for target in flat_geometry:
|
|
|
|
if type(target) == LineString or type(target) == LinearRing:
|
|
|
|
diffs.append(target.difference(toolgeo))
|
|
|
|
else:
|
|
|
|
log.warning("Not implemented.")
|
|
|
|
|
2019-05-17 14:17:58 +00:00
|
|
|
return unary_union(diffs)
|
2019-05-12 22:57:37 +00:00
|
|
|
|
2019-01-03 19:25:08 +00:00
|
|
|
def reset_fields(self):
|
|
|
|
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
2019-05-17 14:17:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
def flatten(geometry):
|
|
|
|
"""
|
|
|
|
Creates a list of non-iterable linear geometry objects.
|
|
|
|
Polygons are expanded into its exterior and interiors.
|
|
|
|
|
|
|
|
Results are placed in self.flat_geometry
|
|
|
|
|
|
|
|
:param geometry: Shapely type or list or list of list of such.
|
|
|
|
"""
|
|
|
|
flat_geo = []
|
|
|
|
try:
|
|
|
|
for geo in geometry:
|
|
|
|
if type(geo) == Polygon:
|
|
|
|
flat_geo.append(geo.exterior)
|
|
|
|
for subgeo in geo.interiors:
|
|
|
|
flat_geo.append(subgeo)
|
|
|
|
else:
|
|
|
|
flat_geo.append(geo)
|
|
|
|
except TypeError:
|
|
|
|
if type(geometry) == Polygon:
|
|
|
|
flat_geo.append(geometry.exterior)
|
|
|
|
for subgeo in geometry.interiors:
|
|
|
|
flat_geo.append(subgeo)
|
|
|
|
else:
|
|
|
|
flat_geo.append(geometry)
|
|
|
|
|
|
|
|
return flat_geo
|
|
|
|
|
|
|
|
|
|
|
|
def recursive_bounds(geometry):
|
|
|
|
"""
|
|
|
|
Returns coordinates of rectangular bounds
|
|
|
|
of geometry: (xmin, ymin, xmax, ymax).
|
|
|
|
"""
|
|
|
|
|
|
|
|
# now it can get bounds for nested lists of objects
|
|
|
|
|
|
|
|
def bounds_rec(obj):
|
|
|
|
try:
|
|
|
|
minx = Inf
|
|
|
|
miny = Inf
|
|
|
|
maxx = -Inf
|
|
|
|
maxy = -Inf
|
|
|
|
|
|
|
|
for k in obj:
|
|
|
|
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
|
|
|
|
minx = min(minx, minx_)
|
|
|
|
miny = min(miny, miny_)
|
|
|
|
maxx = max(maxx, maxx_)
|
|
|
|
maxy = max(maxy, maxy_)
|
|
|
|
return minx, miny, maxx, maxy
|
|
|
|
except TypeError:
|
|
|
|
# it's a Shapely object, return it's bounds
|
|
|
|
return obj.bounds
|
|
|
|
|
|
|
|
return bounds_rec(geometry)
|