- all tuple entries in the Preferences UI are now protected against letter entry

- all entries in the Preferences UI that have numerical entry are protected now against letters
- cleaned the Preferences UI in the Gerber area
This commit is contained in:
Marius Stanciu 2020-05-27 00:27:10 +03:00 committed by Marius
parent dbd1098329
commit c27a2d29e7
26 changed files with 796 additions and 1318 deletions

View File

@ -570,10 +570,13 @@ class FCEntry3(FCEntry):
class EvalEntry(QtWidgets.QLineEdit):
def __init__(self, parent=None):
def __init__(self, border_color=None, parent=None):
super(EvalEntry, self).__init__(parent)
self.readyToEdit = True
if border_color:
self.setStyleSheet("QLineEdit {border: 1px solid %s;}" % border_color)
self.editingFinished.connect(self.on_edit_finished)
def on_edit_finished(self):
@ -639,7 +642,7 @@ class EvalEntry2(QtWidgets.QLineEdit):
def get_value(self):
raw = str(self.text()).strip(' ')
evaled = 0.0
try:
evaled = eval(raw)
except Exception as e:
@ -660,8 +663,8 @@ class NumericalEvalEntry(EvalEntry):
"""
Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
"""
def __init__(self):
super().__init__()
def __init__(self, border_color=None):
super().__init__(border_color=border_color)
regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s]*")
validator = QtGui.QRegExpValidator(regex, self)
@ -672,8 +675,8 @@ class NumericalEvalTupleEntry(EvalEntry):
"""
Will evaluate the input and return a value. Accepts only float numbers and formulas using the operators: /,*,+,-,%
"""
def __init__(self):
super().__init__()
def __init__(self, border_color=None):
super().__init__(border_color=border_color)
regex = QtCore.QRegExp("[0-9\/\*\+\-\%\.\s\,]*")
validator = QtGui.QRegExpValidator(regex, self)

View File

@ -114,7 +114,7 @@ class ObjectUI(QtWidgets.QWidget):
self.common_grid.addWidget(self.transform_label, 2, 0, 1, 2)
# ### Scale ####
self.scale_entry = NumericalEvalEntry()
self.scale_entry = NumericalEvalEntry(border_color='#0069A9')
self.scale_entry.set_value(1.0)
self.scale_entry.setToolTip(
_("Factor by which to multiply\n"
@ -132,7 +132,7 @@ class ObjectUI(QtWidgets.QWidget):
self.common_grid.addWidget(self.scale_button, 3, 1)
# ### Offset ####
self.offsetvector_entry = NumericalEvalTupleEntry()
self.offsetvector_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.offsetvector_entry.setText("(0.0, 0.0)")
self.offsetvector_entry.setToolTip(
_("Amount by which to move the object\n"
@ -206,15 +206,17 @@ class GerberObjectUI(ObjectUI):
grid0.addWidget(self.multicolored_cb, 0, 2)
# Plot CB
self.plot_cb = FCCheckBox('%s' % _("Plot"))
# self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft)
self.plot_cb.setToolTip(_("Plot (show) this object."))
self.plot_lbl = FCLabel('%s' % _("Plot"))
self.plot_lbl.setToolTip(_("Plot (show) this object."))
self.plot_cb = FCCheckBox()
grid0.addWidget(self.plot_cb, 1, 0, 1, 2)
grid0.addWidget(self.plot_lbl, 1, 0)
grid0.addWidget(self.plot_cb, 1, 1)
# ## Object name
self.name_hlay = QtWidgets.QHBoxLayout()
self.custom_box.addLayout(self.name_hlay)
name_label = QtWidgets.QLabel("<b>%s:</b>" % _("Name"))
self.name_entry = FCEntry()
self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus)
@ -225,7 +227,7 @@ class GerberObjectUI(ObjectUI):
self.custom_box.addLayout(hlay_plot)
# ### Gerber Apertures ####
self.apertures_table_label = QtWidgets.QLabel('<b>%s:</b>' % _('Apertures'))
self.apertures_table_label = QtWidgets.QLabel('%s:' % _('Apertures'))
self.apertures_table_label.setToolTip(
_("Apertures Table for the Gerber Object.")
)
@ -282,253 +284,21 @@ class GerberObjectUI(ObjectUI):
# start with apertures table hidden
self.apertures_table.setVisible(False)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.custom_box.addWidget(separator_line)
# Isolation Routing
self.isolation_routing_label = QtWidgets.QLabel("<b>%s</b>" % _("Isolation Routing"))
self.isolation_routing_label.setToolTip(
_("Create a Geometry object with\n"
"toolpaths to cut outside polygons.")
)
self.custom_box.addWidget(self.isolation_routing_label)
# ###########################################
# ########## NEW GRID #######################
# ###########################################
grid1 = QtWidgets.QGridLayout()
self.custom_box.addLayout(grid1)
grid1.setColumnStretch(0, 0)
grid1.setColumnStretch(1, 1)
grid1.setColumnStretch(2, 1)
# Tool Type
self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
self.tool_type_label.setToolTip(
_("Choose which tool to use for Gerber isolation:\n"
"'Circular' or 'V-shape'.\n"
"When the 'V-shape' is selected then the tool\n"
"diameter will depend on the chosen cut depth.")
)
self.tool_type_radio = RadioSet([{'label': _('Circular'), 'value': 'circular'},
{'label': _('V-Shape'), 'value': 'v'}])
grid1.addWidget(self.tool_type_label, 0, 0)
grid1.addWidget(self.tool_type_radio, 0, 1, 1, 2)
# Tip Dia
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
self.tipdialabel.setToolTip(
_("The tip diameter for V-Shape Tool")
)
self.tipdia_spinner = FCDoubleSpinner(callback=self.confirmation_message)
self.tipdia_spinner.set_range(-99.9999, 99.9999)
self.tipdia_spinner.set_precision(self.decimals)
self.tipdia_spinner.setSingleStep(0.1)
self.tipdia_spinner.setWrapping(True)
grid1.addWidget(self.tipdialabel, 1, 0)
grid1.addWidget(self.tipdia_spinner, 1, 1, 1, 2)
# Tip Angle
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
self.tipanglelabel.setToolTip(
_("The tip angle for V-Shape Tool.\n"
"In degree.")
)
self.tipangle_spinner = FCDoubleSpinner(callback=self.confirmation_message)
self.tipangle_spinner.set_range(1, 180)
self.tipangle_spinner.set_precision(self.decimals)
self.tipangle_spinner.setSingleStep(5)
self.tipangle_spinner.setWrapping(True)
grid1.addWidget(self.tipanglelabel, 2, 0)
grid1.addWidget(self.tipangle_spinner, 2, 1, 1, 2)
# Cut Z
self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
self.cutzlabel.setToolTip(
_("Cutting depth (negative)\n"
"below the copper surface.")
)
self.cutz_spinner = FCDoubleSpinner(callback=self.confirmation_message)
self.cutz_spinner.set_range(-9999.9999, 0.0000)
self.cutz_spinner.set_precision(self.decimals)
self.cutz_spinner.setSingleStep(0.1)
self.cutz_spinner.setWrapping(True)
grid1.addWidget(self.cutzlabel, 3, 0)
grid1.addWidget(self.cutz_spinner, 3, 1, 1, 2)
# Tool diameter
tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
tdlabel.setToolTip(
_("Diameter of the cutting tool.\n"
"If you want to have an isolation path\n"
"inside the actual shape of the Gerber\n"
"feature, use a negative value for\n"
"this parameter.")
)
tdlabel.setMinimumWidth(90)
self.iso_tool_dia_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.iso_tool_dia_entry.set_range(-9999.9999, 9999.9999)
self.iso_tool_dia_entry.set_precision(self.decimals)
self.iso_tool_dia_entry.setSingleStep(0.1)
grid1.addWidget(tdlabel, 4, 0)
grid1.addWidget(self.iso_tool_dia_entry, 4, 1, 1, 2)
# Number of Passes
passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
passlabel.setToolTip(
_("Width of the isolation gap in\n"
"number (integer) of tool widths.")
)
passlabel.setMinimumWidth(90)
self.iso_width_entry = FCSpinner(callback=self.confirmation_message_int)
self.iso_width_entry.set_range(1, 999)
grid1.addWidget(passlabel, 5, 0)
grid1.addWidget(self.iso_width_entry, 5, 1, 1, 2)
# Pass overlap
overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
overlabel.setToolTip(
_("How much (percentage) of the tool width to overlap each tool pass.")
)
overlabel.setMinimumWidth(90)
self.iso_overlap_entry = FCDoubleSpinner(suffix='%', callback=self.confirmation_message)
self.iso_overlap_entry.set_precision(self.decimals)
self.iso_overlap_entry.setWrapping(True)
self.iso_overlap_entry.set_range(0.0000, 99.9999)
self.iso_overlap_entry.setSingleStep(0.1)
grid1.addWidget(overlabel, 6, 0)
grid1.addWidget(self.iso_overlap_entry, 6, 1, 1, 2)
# Milling Type Radio Button
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
self.milling_type_label.setToolTip(
_("Milling type:\n"
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conventional'), 'value': 'cv'}])
grid1.addWidget(self.milling_type_label, 7, 0)
grid1.addWidget(self.milling_type_radio, 7, 1, 1, 2)
# combine all passes CB
self.combine_passes_cb = FCCheckBox(label=_('Combine'))
self.combine_passes_cb.setToolTip(
_("Combine all passes into one object")
)
# generate follow
self.follow_cb = FCCheckBox(label=_('"Follow"'))
self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace."))
self.follow_lbl = FCLabel('%s:' % _("Follow"))
self.follow_lbl.setToolTip(_("Generate a 'Follow' geometry.\n"
"This means that it will cut through\n"
"the middle of the trace."))
self.follow_lbl.setMinimumWidth(90)
self.follow_cb = FCCheckBox()
# avoid an area from isolation
self.except_cb = FCCheckBox(label=_('Except'))
self.except_cb.setToolTip(_("When the isolation geometry is generated,\n"
"by checking this, the area of the object below\n"
"will be subtracted from the isolation geometry."))
grid1.addWidget(self.combine_passes_cb, 8, 0)
grid1.addWidget(self.follow_cb, 8, 1)
grid1.addWidget(self.except_cb, 8, 2)
# ## Form Layout
form_layout = QtWidgets.QFormLayout()
grid1.addLayout(form_layout, 9, 0, 1, 3)
# ################################################
# ##### Type of object to be excepted ############
# ################################################
self.type_obj_combo = FCComboBox()
self.type_obj_combo.addItems([_("Gerber"), _("Geometry")])
# we get rid of item1 ("Excellon") as it is not suitable
# self.type_obj_combo.view().setRowHidden(1, True)
self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.resource_loc + "/flatcam_icon16.png"))
self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.resource_loc + "/geometry16.png"))
self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Obj Type"))
self.type_obj_combo_label.setToolTip(
_("Specify the type of object to be excepted from isolation.\n"
"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.")
)
# self.type_obj_combo_label.setMinimumWidth(60)
form_layout.addRow(self.type_obj_combo_label, self.type_obj_combo)
# ################################################
# ##### The object to be excepted ################
# ################################################
self.obj_combo = FCComboBox()
self.obj_label = QtWidgets.QLabel('%s:' % _("Object"))
self.obj_label.setToolTip(_("Object whose area will be removed from isolation geometry."))
form_layout.addRow(self.obj_label, self.obj_combo)
# ---------------------------------------------- #
# --------- Isolation scope -------------------- #
# ---------------------------------------------- #
self.iso_scope_label = QtWidgets.QLabel('<b>%s:</b>' % _('Scope'))
self.iso_scope_label.setToolTip(
_("Isolation scope. Choose what to isolate:\n"
"- 'All' -> Isolate all the polygons in the object\n"
"- 'Selection' -> Isolate a selection of polygons.")
)
self.iso_scope_radio = RadioSet([{'label': _('All'), 'value': 'all'},
{'label': _('Selection'), 'value': 'single'}])
grid1.addWidget(self.iso_scope_label, 10, 0)
grid1.addWidget(self.iso_scope_radio, 10, 1, 1, 2)
# ---------------------------------------------- #
# --------- Isolation type -------------------- #
# ---------------------------------------------- #
self.iso_type_label = QtWidgets.QLabel('<b>%s:</b>' % _('Isolation Type'))
self.iso_type_label.setToolTip(
_("Choose how the isolation will be executed:\n"
"- 'Full' -> complete isolation of polygons\n"
"- 'Ext' -> will isolate only on the outside\n"
"- 'Int' -> will isolate only on the inside\n"
"'Exterior' isolation is almost always possible\n"
"(with the right tool) but 'Interior'\n"
"isolation can be done only when there is an opening\n"
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
)
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
{'label': _('Ext'), 'value': 'ext'},
{'label': _('Int'), 'value': 'int'}])
grid1.addWidget(self.iso_type_label, 11, 0)
grid1.addWidget(self.iso_type_radio, 11, 1, 1, 2)
self.generate_iso_button = QtWidgets.QPushButton("%s" % _("Generate Isolation Geometry"))
self.generate_iso_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.generate_iso_button.setToolTip(
_("Create a Geometry object with toolpaths to cut \n"
"isolation outside, inside or on both sides of the\n"
"object. For a Gerber object outside means outside\n"
"of the Gerber feature and inside means inside of\n"
"the Gerber feature, if possible at all. This means\n"
"that only if the Gerber feature has openings inside, they\n"
"will be isolated. If what is wanted is to cut isolation\n"
"inside the actual Gerber feature, use a negative tool\n"
"diameter above.")
)
grid1.addWidget(self.generate_iso_button, 12, 0, 1, 3)
hf_lay = QtWidgets.QHBoxLayout()
self.custom_box.addLayout(hf_lay)
hf_lay.addWidget(self.follow_lbl)
hf_lay.addWidget(self.follow_cb)
hf_lay.addStretch()
# Buffer Geometry
self.create_buffer_button = QtWidgets.QPushButton(_('Buffer Solid Geometry'))
self.create_buffer_button.setToolTip(
_("This button is shown only when the Gerber file\n"
@ -536,19 +306,12 @@ class GerberObjectUI(ObjectUI):
"Clicking this will create the buffered geometry\n"
"required for isolation.")
)
grid1.addWidget(self.create_buffer_button, 13, 0, 1, 3)
self.custom_box.addWidget(self.create_buffer_button)
self.ohis_iso = OptionalHideInputSection(
self.except_cb,
[self.type_obj_combo, self.type_obj_combo_label, self.obj_combo, self.obj_label],
logic=True
)
separator_line2 = QtWidgets.QFrame()
separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line2, 14, 0, 1, 3)
# grid1.addWidget(QtWidgets.QLabel(''), 15, 0)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.custom_box.addWidget(separator_line)
# ###########################################
# ########## NEW GRID #######################

View File

@ -330,9 +330,9 @@ class PreferencesUIManager:
"tools_iso_tooldia": self.ui.tools_defaults_form.tools_iso_group.tool_dia_entry,
"tools_iso_order": self.ui.tools_defaults_form.tools_iso_group.order_radio,
"tools_iso_tool_type": self.ui.tools_defaults_form.tools_iso_group.tool_type_radio,
"tools_iso_tool_vtipdia": self.ui.tools_defaults_form.tools_iso_group.tipdia_spinner,
"tools_iso_tool_vtipangle": self.ui.tools_defaults_form.tools_iso_group.tipangle_spinner,
"tools_iso_tool_cutz": self.ui.tools_defaults_form.tools_iso_group.cutz_spinner,
"tools_iso_tool_vtipdia": self.ui.tools_defaults_form.tools_iso_group.tipdia_entry,
"tools_iso_tool_vtipangle": self.ui.tools_defaults_form.tools_iso_group.tipangle_entry,
"tools_iso_tool_cutz": self.ui.tools_defaults_form.tools_iso_group.cutz_entry,
"tools_iso_newdia": self.ui.tools_defaults_form.tools_iso_group.newdia_entry,
"tools_iso_passes": self.ui.tools_defaults_form.tools_iso_group.passes_entry,
@ -346,6 +346,7 @@ class PreferencesUIManager:
"tools_iso_isoexcept": self.ui.tools_defaults_form.tools_iso_group.except_cb,
"tools_iso_selection": self.ui.tools_defaults_form.tools_iso_group.select_combo,
"tools_iso_area_shape": self.ui.tools_defaults_form.tools_iso_group.area_shape_radio,
"tools_iso_plotting": self.ui.tools_defaults_form.tools_iso_group.plotting_radio,
# NCC Tool
"tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry,
@ -360,13 +361,13 @@ class PreferencesUIManager:
"tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner,
"tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.select_combo,
"tools_ncc_area_shape": self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio,
"tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio,
"tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio,
"tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio,
"tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry,
"tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry,
"tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry,
"tools_nccnewdia": self.ui.tools_defaults_form.tools_ncc_group.newdia_entry,
"tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.plotting_radio,
# CutOut Tool
"tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry,

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCDoubleSpinner, FCEntry, FloatEntry, RadioSet, FCCheckBox
from AppGUI.GUIElements import FCDoubleSpinner, RadioSet, FCCheckBox, NumericalEvalTupleEntry, NumericalEvalEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
import AppTranslation as fcTranslate
@ -60,7 +60,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
toolchange_xy_label.setToolTip(
_("Toolchange X,Y position.")
)
self.toolchangexy_entry = FCEntry()
self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid1.addWidget(toolchange_xy_label, 1, 0)
grid1.addWidget(self.toolchangexy_entry, 1, 1)
@ -71,7 +71,7 @@ class ExcellonAdvOptPrefGroupUI(OptionsGroupUI):
_("Height of the tool just after start.\n"
"Delete the value if you don't need this feature.")
)
self.estartz_entry = FloatEntry()
self.estartz_entry = NumericalEvalEntry(border_color='#0069A9')
grid1.addWidget(startzlabel, 2, 0)
grid1.addWidget(self.estartz_entry, 2, 1)

View File

@ -2,7 +2,7 @@ from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QSettings
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry, FCSpinner, OptionalInputSection, \
FCComboBox
FCComboBox, NumericalEvalTupleEntry
from AppGUI.preferences import machinist_setting
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -198,7 +198,7 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI):
"If no value is entered then there is no move\n"
"on X,Y plane at the end of the job.")
)
self.endxy_entry = FCEntry()
self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid2.addWidget(endmove_xy_label, 9, 0)
grid2.addWidget(self.endxy_entry, 9, 1)

View File

@ -1,7 +1,8 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCEntry, FloatEntry, FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCLabel, NumericalEvalTupleEntry, \
NumericalEvalEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -46,8 +47,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
toolchange_xy_label.setToolTip(
_("Toolchange X,Y position.")
)
self.toolchangexy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid1.addWidget(toolchange_xy_label, 1, 0)
self.toolchangexy_entry = FCEntry()
grid1.addWidget(self.toolchangexy_entry, 1, 1)
# Start move Z
@ -56,8 +58,9 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
_("Height of the tool just after starting the work.\n"
"Delete the value if you don't need this feature.")
)
self.gstartz_entry = NumericalEvalEntry(border_color='#0069A9')
grid1.addWidget(startzlabel, 2, 0)
self.gstartz_entry = FloatEntry()
grid1.addWidget(self.gstartz_entry, 2, 1)
# Feedrate rapids
@ -186,6 +189,11 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
grid1.addWidget(segy_label, 11, 0)
grid1.addWidget(self.segy_entry, 11, 1)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid1.addWidget(separator_line, 12, 0, 1, 2)
# -----------------------------
# --- Area Exclusion ----------
# -----------------------------
@ -195,10 +203,10 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
"Those parameters are available only for\n"
"Advanced App. Level.")
)
grid1.addWidget(self.adv_label, 12, 0, 1, 2)
grid1.addWidget(self.adv_label, 13, 0, 1, 2)
# Exclusion Area CB
self.exclusion_cb = FCCheckBox('%s:' % _("Exclusion areas"))
self.exclusion_cb = FCCheckBox('%s' % _("Exclusion areas"))
self.exclusion_cb.setToolTip(
_(
"Include exclusion areas.\n"
@ -206,7 +214,7 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
"is forbidden."
)
)
grid1.addWidget(self.exclusion_cb, 13, 0, 1, 2)
grid1.addWidget(self.exclusion_cb, 14, 0, 1, 2)
# Area Selection shape
self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
@ -217,8 +225,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
{'label': _("Polygon"), 'value': 'polygon'}])
grid1.addWidget(self.area_shape_label, 14, 0)
grid1.addWidget(self.area_shape_radio, 14, 1)
grid1.addWidget(self.area_shape_label, 15, 0)
grid1.addWidget(self.area_shape_radio, 15, 1)
# Chose Strategy
self.strategy_label = FCLabel('%s:' % _("Strategy"))
@ -229,8 +237,8 @@ class GeometryAdvOptPrefGroupUI(OptionsGroupUI):
self.strategy_radio = RadioSet([{'label': _('Over'), 'value': 'over'},
{'label': _('Around'), 'value': 'around'}])
grid1.addWidget(self.strategy_label, 15, 0)
grid1.addWidget(self.strategy_radio, 15, 1)
grid1.addWidget(self.strategy_label, 16, 0)
grid1.addWidget(self.strategy_radio, 16, 1)
# Over Z
self.over_z_label = FCLabel('%s:' % _("Over Z"))

View File

@ -1,7 +1,8 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QSettings
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCEntry, FCSpinner, FCComboBox
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCSpinner, FCComboBox, \
NumericalEvalTupleEntry
from AppGUI.preferences import machinist_setting
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
@ -176,7 +177,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI):
"If no value is entered then there is no move\n"
"on X,Y plane at the end of the job.")
)
self.endxy_entry = FCEntry()
self.endxy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid1.addWidget(endmove_xy_label, 7, 0)
grid1.addWidget(self.endxy_entry, 7, 1)

View File

@ -63,85 +63,6 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI):
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 2, 0, 1, 2)
# Tool Type
self.tool_type_label = QtWidgets.QLabel('<b>%s</b>' % _('Tool Type'))
self.tool_type_label.setToolTip(
_("Choose which tool to use for Gerber isolation:\n"
"'Circular' or 'V-shape'.\n"
"When the 'V-shape' is selected then the tool\n"
"diameter will depend on the chosen cut depth.")
)
self.tool_type_radio = RadioSet([{'label': 'Circular', 'value': 'circular'},
{'label': 'V-Shape', 'value': 'v'}])
grid0.addWidget(self.tool_type_label, 3, 0)
grid0.addWidget(self.tool_type_radio, 3, 1, 1, 2)
# Tip Dia
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
self.tipdialabel.setToolTip(
_("The tip diameter for V-Shape Tool")
)
self.tipdia_spinner = FCDoubleSpinner()
self.tipdia_spinner.set_precision(self.decimals)
self.tipdia_spinner.set_range(-99.9999, 99.9999)
self.tipdia_spinner.setSingleStep(0.1)
self.tipdia_spinner.setWrapping(True)
grid0.addWidget(self.tipdialabel, 4, 0)
grid0.addWidget(self.tipdia_spinner, 4, 1, 1, 2)
# Tip Angle
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
self.tipanglelabel.setToolTip(
_("The tip angle for V-Shape Tool.\n"
"In degree.")
)
self.tipangle_spinner = FCSpinner()
self.tipangle_spinner.set_range(1, 180)
self.tipangle_spinner.set_step(5)
self.tipangle_spinner.setWrapping(True)
grid0.addWidget(self.tipanglelabel, 5, 0)
grid0.addWidget(self.tipangle_spinner, 5, 1, 1, 2)
# Cut Z
self.cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
self.cutzlabel.setToolTip(
_("Cutting depth (negative)\n"
"below the copper surface.")
)
self.cutz_spinner = FCDoubleSpinner()
self.cutz_spinner.set_precision(self.decimals)
self.cutz_spinner.set_range(-99.9999, 0.0000)
self.cutz_spinner.setSingleStep(0.1)
self.cutz_spinner.setWrapping(True)
grid0.addWidget(self.cutzlabel, 6, 0)
grid0.addWidget(self.cutz_spinner, 6, 1, 1, 2)
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
_("Choose how the isolation will be executed:\n"
"- 'Full' -> complete isolation of polygons\n"
"- 'Ext' -> will isolate only on the outside\n"
"- 'Int' -> will isolate only on the inside\n"
"'Exterior' isolation is almost always possible\n"
"(with the right tool) but 'Interior'\n"
"isolation can be done only when there is an opening\n"
"inside of the polygon (e.g polygon is a 'doughnut' shape).")
)
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
{'label': _('Exterior'), 'value': 'ext'},
{'label': _('Interior'), 'value': 'int'}])
grid0.addWidget(self.iso_type_label, 7, 0,)
grid0.addWidget(self.iso_type_radio, 7, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 8, 0, 1, 2)
# Buffering Type
buffering_label = QtWidgets.QLabel('%s:' % _('Buffering'))
buffering_label.setToolTip(

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet
from AppGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCEntry, RadioSet, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -109,8 +109,9 @@ class GerberEditorPrefGroupUI(OptionsGroupUI):
"The value of the diameter has to use the dot decimals separator.\n"
"Valid values: 0.3, 1.0")
)
self.adddim_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid0.addWidget(self.adddim_label, 5, 0)
self.adddim_entry = FCEntry()
grid0.addWidget(self.adddim_entry, 5, 1)
self.grb_array_linear_label = QtWidgets.QLabel('<b>%s:</b>' % _('Linear Pad Array'))

View File

@ -28,96 +28,6 @@ class GerberOptPrefGroupUI(OptionsGroupUI):
self.setTitle(str(_("Gerber Options")))
# ## Isolation Routing
self.isolation_routing_label = QtWidgets.QLabel("<b>%s:</b>" % _("Isolation Routing"))
self.isolation_routing_label.setToolTip(
_("Create a Geometry object with\n"
"toolpaths to cut outside polygons.")
)
self.layout.addWidget(self.isolation_routing_label)
# Cutting Tool Diameter
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)
tdlabel = QtWidgets.QLabel('%s:' % _('Tool dia'))
tdlabel.setToolTip(
_("Diameter of the cutting tool.")
)
grid0.addWidget(tdlabel, 0, 0)
self.iso_tool_dia_entry = FCDoubleSpinner()
self.iso_tool_dia_entry.set_precision(self.decimals)
self.iso_tool_dia_entry.setSingleStep(0.1)
self.iso_tool_dia_entry.set_range(-9999, 9999)
grid0.addWidget(self.iso_tool_dia_entry, 0, 1)
# Nr of passes
passlabel = QtWidgets.QLabel('%s:' % _('# Passes'))
passlabel.setToolTip(
_("Width of the isolation gap in\n"
"number (integer) of tool widths.")
)
self.iso_width_entry = FCSpinner()
self.iso_width_entry.set_range(1, 999)
grid0.addWidget(passlabel, 1, 0)
grid0.addWidget(self.iso_width_entry, 1, 1)
# Pass overlap
overlabel = QtWidgets.QLabel('%s:' % _('Pass overlap'))
overlabel.setToolTip(
_("How much (percentage) of the tool width to overlap each tool pass.")
)
self.iso_overlap_entry = FCDoubleSpinner(suffix='%')
self.iso_overlap_entry.set_precision(self.decimals)
self.iso_overlap_entry.setWrapping(True)
self.iso_overlap_entry.setRange(0.0000, 99.9999)
self.iso_overlap_entry.setSingleStep(0.1)
grid0.addWidget(overlabel, 2, 0)
grid0.addWidget(self.iso_overlap_entry, 2, 1)
# Isolation Scope
self.select_label = QtWidgets.QLabel('%s:' % _('Selection'))
self.select_label.setToolTip(
_("Isolation scope. Choose what to isolate:\n"
"- 'All' -> Isolate all the polygons in the object\n"
"- 'Selection' -> Isolate a selection of polygons.\n"
"- 'Reference Object' - will process the area specified by another object.")
)
self.select_combo = FCComboBox()
self.select_combo.addItems(
[_("All"), _("Area Selection"), _("Reference Object")]
)
grid0.addWidget(self.select_label, 3, 0)
grid0.addWidget(self.select_combo, 3, 1, 1, 2)
# Milling Type
milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
milling_type_label.setToolTip(
_("Milling type:\n"
"- climb / best for precision milling and to reduce tool usage\n"
"- conventional / useful when there is no backlash compensation")
)
grid0.addWidget(milling_type_label, 4, 0)
self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'},
{'label': _('Conventional'), 'value': 'cv'}])
grid0.addWidget(self.milling_type_radio, 4, 1)
# Combine passes
self.combine_passes_cb = FCCheckBox(label=_('Combine Passes'))
self.combine_passes_cb.setToolTip(
_("Combine all passes into one object")
)
grid0.addWidget(self.combine_passes_cb, 5, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 6, 0, 1, 2)
# ## Clear non-copper regions
self.clearcopper_label = QtWidgets.QLabel("<b>%s:</b>" % _("Non-copper regions"))
self.clearcopper_label.setToolTip(

View File

@ -44,6 +44,7 @@ class GerberPreferencesUI(QtWidgets.QWidget):
self.vlay = QtWidgets.QVBoxLayout()
self.vlay.addWidget(self.gerber_opt_group)
self.vlay.addWidget(self.gerber_exp_group)
self.vlay.addStretch()
self.layout.addWidget(self.gerber_gen_group)
self.layout.addLayout(self.vlay)

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCEntry
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -116,7 +116,7 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
"(x, y) point will be used,")
)
self.toolchange_xy_entry = FCEntry()
self.toolchange_xy_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid_lay.addWidget(toolchangexy_lbl, 7, 0)
grid_lay.addWidget(self.toolchange_xy_entry, 7, 1, 1, 2)

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -32,7 +32,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
_("Create a Geometry object with\n"
"toolpaths to cut around polygons.")
)
self.layout.addWidget(self.clearcopper_label)
self.layout.addWidget(self.iso_label)
grid0 = QtWidgets.QGridLayout()
self.layout.addLayout(grid0)
@ -44,11 +44,11 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
"The value of the diameter has to use the dot decimals separator.\n"
"Valid values: 0.3, 1.0")
)
self.tool_dia_entry = FCEntry(border_color='#0069A9')
self.tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.tool_dia_entry.setPlaceholderText(_("Comma separated values"))
grid0.addWidget(isotdlabel, 0, 0)
grid0.addWidget(self.tool_dia_entry, 0, 1)
grid0.addWidget(self.tool_dia_entry, 0, 1, 1, 2)
# Tool order Radio Button
self.order_label = QtWidgets.QLabel('%s:' % _('Tool order'))
@ -64,7 +64,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
{'label': _('Reverse'), 'value': 'rev'}])
grid0.addWidget(self.order_label, 1, 0)
grid0.addWidget(self.order_radio, 1, 1)
grid0.addWidget(self.order_radio, 1, 1, 1, 2)
# Tool Type Radio Button
self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type'))
@ -83,7 +83,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.tool_type_label, 2, 0)
grid0.addWidget(self.tool_type_radio, 2, 1)
grid0.addWidget(self.tool_type_radio, 2, 1, 1, 2)
# Tip Dia
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
@ -95,7 +95,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.tipdia_entry.setSingleStep(0.1)
grid0.addWidget(self.tipdialabel, 3, 0)
grid0.addWidget(self.tipdia_entry, 3, 1)
grid0.addWidget(self.tipdia_entry, 3, 1, 1, 2)
# Tip Angle
self.tipanglelabel = QtWidgets.QLabel('%s:' % _('V-Tip Angle'))
@ -109,7 +109,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.tipangle_entry.setWrapping(True)
grid0.addWidget(self.tipanglelabel, 4, 0)
grid0.addWidget(self.tipangle_entry, 4, 1)
grid0.addWidget(self.tipangle_entry, 4, 1, 1, 2)
# Cut Z entry
cutzlabel = QtWidgets.QLabel('%s:' % _('Cut Z'))
@ -128,7 +128,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(cutzlabel, 5, 0)
grid0.addWidget(self.cutz_entry, 5, 1)
grid0.addWidget(self.cutz_entry, 5, 1, 1, 2)
# New Diameter
self.newdialabel = QtWidgets.QLabel('%s:' % _('New Dia'))
@ -143,12 +143,12 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.newdia_entry.setSingleStep(0.1)
grid0.addWidget(self.newdialabel, 6, 0)
grid0.addWidget(self.newdia_entry, 6, 1)
grid0.addWidget(self.newdia_entry, 6, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 7, 0, 1, 2)
grid0.addWidget(separator_line, 7, 0, 1, 3)
# Passes
passlabel = QtWidgets.QLabel('%s:' % _('Passes'))
@ -161,7 +161,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.passes_entry.setObjectName("i_passes")
grid0.addWidget(passlabel, 8, 0)
grid0.addWidget(self.passes_entry, 8, 1)
grid0.addWidget(self.passes_entry, 8, 1, 1, 2)
# Overlap Entry
overlabel = QtWidgets.QLabel('%s:' % _('Overlap'))
@ -176,7 +176,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.overlap_entry.setObjectName("i_overlap")
grid0.addWidget(overlabel, 9, 0)
grid0.addWidget(self.overlap_entry, 9, 1)
grid0.addWidget(self.overlap_entry, 9, 1, 1, 2)
# Milling Type Radio Button
self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type'))
@ -195,7 +195,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(self.milling_type_label, 10, 0)
grid0.addWidget(self.milling_type_radio, 10, 1)
grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2)
# Follow
self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
@ -212,7 +212,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.follow_cb.setObjectName("i_follow")
grid0.addWidget(self.follow_label, 11, 0)
grid0.addWidget(self.follow_cb, 11, 1)
grid0.addWidget(self.follow_cb, 11, 1, 1, 2)
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
@ -232,15 +232,15 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.iso_type_radio.setObjectName("i_type")
grid0.addWidget(self.iso_type_label, 12, 0)
grid0.addWidget(self.iso_type_radio, 12, 1)
grid0.addWidget(self.iso_type_radio, 12, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 13, 0, 1, 2)
grid0.addWidget(separator_line, 13, 0, 1, 3)
# Rest machining CheckBox
self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.rest_cb = FCCheckBox('%s' % _("Rest"))
self.rest_cb.setObjectName("i_rest_machining")
self.rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
@ -252,7 +252,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
"If not checked, use the standard algorithm.")
)
grid0.addWidget(self.ncc_rest_cb, 17, 0, 1, 2)
grid0.addWidget(self.rest_cb, 17, 0)
# Combine All Passes
self.combine_passes_cb = FCCheckBox(label=_('Combine'))
@ -261,7 +261,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
)
self.combine_passes_cb.setObjectName("i_combine")
grid0.addWidget(self.combine_passes_cb, 18, 0, 1, 2)
grid0.addWidget(self.combine_passes_cb, 17, 1)
# Exception Areas
self.except_cb = FCCheckBox(label=_('Except'))
@ -269,7 +269,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
"by checking this, the area of the object below\n"
"will be subtracted from the isolation geometry."))
self.except_cb.setObjectName("i_except")
grid0.addWidget(self.except_cb, 19, 0, 1, 2)
grid0.addWidget(self.except_cb, 17, 2)
# Isolation Scope
self.select_label = QtWidgets.QLabel('%s:' % _("Selection"))
@ -286,7 +286,7 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
self.select_combo.setObjectName("i_selection")
grid0.addWidget(self.select_label, 20, 0)
grid0.addWidget(self.select_combo, 20, 1)
grid0.addWidget(self.select_combo, 20, 1, 1, 2)
# Area Shape
self.area_shape_label = QtWidgets.QLabel('%s:' % _("Shape"))
@ -298,22 +298,22 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
{'label': _("Polygon"), 'value': 'polygon'}])
grid0.addWidget(self.area_shape_label, 21, 0)
grid0.addWidget(self.area_shape_radio, 21, 1)
grid0.addWidget(self.area_shape_radio, 21, 1, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 22, 0, 1, 2)
grid0.addWidget(separator_line, 22, 0, 1, 3)
# ## Plotting type
self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("ISO Plotting"))
self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
plotting_label.setToolTip(
_("- 'Normal' - normal plotting, done at the end of the job\n"
"- 'Progressive' - each shape is plotted after it is generated")
)
grid0.addWidget(plotting_label, 21, 0)
grid0.addWidget(self.ncc_plotting_radio, 21, 1)
grid0.addWidget(plotting_label, 23, 0)
grid0.addWidget(self.plotting_radio, 23, 1, 1, 2)
self.layout.addStretch()

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -45,7 +45,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
"Valid values: 0.3, 1.0")
)
grid0.addWidget(ncctdlabel, 0, 0)
self.ncc_tool_dia_entry = FCEntry(border_color='#0069A9')
self.ncc_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.ncc_tool_dia_entry.setPlaceholderText(_("Comma separated values"))
grid0.addWidget(self.ncc_tool_dia_entry, 0, 1)
@ -285,7 +285,7 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
grid0.addWidget(separator_line, 16, 0, 1, 2)
# Rest machining CheckBox
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
self.ncc_rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
"Basically it will clear copper outside PCB features,\n"
@ -336,14 +336,14 @@ class ToolsNCCPrefGroupUI(OptionsGroupUI):
grid0.addWidget(separator_line, 20, 0, 1, 2)
# ## Plotting type
self.ncc_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("NCC Plotting"))
self.plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
plotting_label.setToolTip(
_("- 'Normal' - normal plotting, done at the end of the job\n"
"- 'Progressive' - each shape is plotted after it is generated")
)
grid0.addWidget(plotting_label, 21, 0)
grid0.addWidget(self.ncc_plotting_radio, 21, 1)
grid0.addWidget(self.plotting_radio, 21, 1)
self.layout.addStretch()

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCEntry, RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox
from AppGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -53,7 +53,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
)
grid0.addWidget(ptdlabel, 0, 0)
self.painttooldia_entry = FCEntry(border_color='#0069A9')
self.painttooldia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.painttooldia_entry.setPlaceholderText(_("Comma separated values"))
grid0.addWidget(self.painttooldia_entry, 0, 1)
@ -241,8 +241,8 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 13, 0, 1, 2)
self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.rest_cb.setObjectName(_("Rest Machining"))
self.rest_cb = FCCheckBox('%s' % _("Rest"))
self.rest_cb.setObjectName(_("Rest"))
self.rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
"Basically it will clear copper outside PCB features,\n"
@ -302,7 +302,7 @@ class ToolsPaintPrefGroupUI(OptionsGroupUI):
# ## Plotting type
self.paint_plotting_radio = RadioSet([{'label': _('Normal'), 'value': 'normal'},
{"label": _("Progressive"), "value": "progressive"}])
plotting_label = QtWidgets.QLabel('%s:' % _("Paint Plotting"))
plotting_label = QtWidgets.QLabel('%s:' % _("Plotting"))
plotting_label.setToolTip(
_("- 'Normal' - normal plotting, done at the end of the job\n"
"- 'Progressive' - each shape is plotted after it is generated")

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCEntry, FCDoubleSpinner, FCSpinner, FCComboBox
from AppGUI.GUIElements import FCDoubleSpinner, FCSpinner, FCComboBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -45,7 +45,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
"The value of the diameter has to use the dot decimals separator.\n"
"Valid values: 0.3, 1.0")
)
self.nozzle_tool_dia_entry = FCEntry()
self.nozzle_tool_dia_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid0.addWidget(nozzletdlabel, 0, 0)
grid0.addWidget(self.nozzle_tool_dia_entry, 0, 1)
@ -130,7 +130,7 @@ class ToolsSolderpastePrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.z_toolchange_entry, 6, 1)
# X,Y Toolchange location
self.xy_toolchange_entry = FCEntry()
self.xy_toolchange_entry = NumericalEvalTupleEntry(border_color='#0069A9')
self.xy_toolchange_label = QtWidgets.QLabel('%s:' % _("Toolchange X-Y"))
self.xy_toolchange_label.setToolTip(
_("The X,Y location for tool (nozzle) change.\n"

View File

@ -1,7 +1,7 @@
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSettings
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCEntry
from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry
from AppGUI.preferences.OptionsGroupUI import OptionsGroupUI
import gettext
@ -191,7 +191,7 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI):
"The 'x' in (x, y) will be used when using Flip on X and\n"
"the 'y' in (x, y) will be used when using Flip on Y and")
)
self.flip_ref_entry = FCEntry()
self.flip_ref_entry = NumericalEvalTupleEntry(border_color='#0069A9')
grid0.addWidget(self.flip_ref_label, 14, 0, 1, 2)
grid0.addWidget(self.flip_ref_entry, 15, 0, 1, 2)

View File

@ -1180,13 +1180,25 @@ class ExcellonObject(FlatCAMObj, Excellon):
def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
"""
Will generate an Geometry Object allowing to cut a drill hole instead of drilling it.
Note: This method is a good template for generic operations as
it takes it's options from parameters or otherwise from the
object's options and returns a (success, msg) tuple as feedback
for shell operations.
:return: Success/failure condition tuple (bool, str).
:rtype: tuple
:param tools: A list of tools where the drills are to be milled or a string: "all"
:type tools:
:param outname: the name of the resulting Geometry object
:type outname: str
:param tooldia: the tool diameter to be used in creation of the milling path (Geometry Object)
:type tooldia: float
:param plot: if to plot the resulting object
:type plot: bool
:param use_thread: if to use threading for creation of the Geometry object
:type use_thread: bool
:return: Success/failure condition tuple (bool, str).
:rtype: tuple
"""
# Get the tools from the list. These are keys
@ -1250,7 +1262,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.options['Tools_in_use'] = tool_table_items
geo_obj.options['type'] = 'Excellon Geometry'
geo_obj.options["cnctooldia"] = str(tooldia)
geo_obj.options["multidepth"] = self.options["multidepth"]
geo_obj.solid_geometry = []
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
@ -1280,15 +1292,27 @@ class ExcellonObject(FlatCAMObj, Excellon):
return True, ""
def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False):
def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False):
"""
Will generate an Geometry Object allowing to cut/mill a slot hole.
Note: This method is a good template for generic operations as
it takes it's options from parameters or otherwise from the
object's options and returns a (success, msg) tuple as feedback
for shell operations.
:return: Success/failure condition tuple (bool, str).
:rtype: tuple
:param tools: A list of tools where the drills are to be milled or a string: "all"
:type tools:
:param outname: the name of the resulting Geometry object
:type outname: str
:param tooldia: the tool diameter to be used in creation of the milling path (Geometry Object)
:type tooldia: float
:param plot: if to plot the resulting object
:type plot: bool
:param use_thread: if to use threading for creation of the Geometry object
:type use_thread: bool
:return: Success/failure condition tuple (bool, str).
:rtype: tuple
"""
# Get the tools from the list. These are keys
@ -1341,7 +1365,7 @@ class ExcellonObject(FlatCAMObj, Excellon):
geo_obj.options['Tools_in_use'] = tool_table_items
geo_obj.options['type'] = 'Excellon Geometry'
geo_obj.options["cnctooldia"] = str(tooldia)
geo_obj.options["multidepth"] = self.options["multidepth"]
geo_obj.solid_geometry = []
# in case that the tool used has the same diameter with the hole, and since the maximum resolution
@ -1388,13 +1412,13 @@ class ExcellonObject(FlatCAMObj, Excellon):
self.app.defaults.report_usage("excellon_on_create_milling_drills button")
self.read_form()
self.generate_milling_drills(use_thread=False)
self.generate_milling_drills(use_thread=False, plot=True)
def on_generate_milling_slots_button_click(self, *args):
self.app.defaults.report_usage("excellon_on_create_milling_slots_button")
self.read_form()
self.generate_milling_slots(use_thread=False)
self.generate_milling_slots(use_thread=False, plot=True)
def on_pp_changed(self):
current_pp = self.ui.pp_excellon_name_cb.get_value()

View File

@ -115,23 +115,12 @@ class GerberObject(FlatCAMObj, Gerber):
"plot": True,
"multicolored": False,
"solid": False,
"tool_type": 'circular',
"vtipdia": 0.1,
"vtipangle": 30,
"vcutz": -0.05,
"isotooldia": 0.016,
"isopasses": 1,
"isooverlap": 15,
"milling_type": "cl",
"combine_passes": True,
"noncoppermargin": 0.0,
"noncopperrounded": False,
"bboxmargin": 0.0,
"bboxrounded": False,
"aperture_display": False,
"follow": False,
"iso_scope": 'all',
"iso_type": 'full'
})
# type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
@ -197,33 +186,22 @@ class GerberObject(FlatCAMObj, Gerber):
"plot": self.ui.plot_cb,
"multicolored": self.ui.multicolored_cb,
"solid": self.ui.solid_cb,
"tool_type": self.ui.tool_type_radio,
"vtipdia": self.ui.tipdia_spinner,
"vtipangle": self.ui.tipangle_spinner,
"vcutz": self.ui.cutz_spinner,
"isotooldia": self.ui.iso_tool_dia_entry,
"isopasses": self.ui.iso_width_entry,
"isooverlap": self.ui.iso_overlap_entry,
"milling_type": self.ui.milling_type_radio,
"combine_passes": self.ui.combine_passes_cb,
"noncoppermargin": self.ui.noncopper_margin_entry,
"noncopperrounded": self.ui.noncopper_rounded_cb,
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb,
"aperture_display": self.ui.aperture_table_visibility_cb,
"follow": self.ui.follow_cb,
"iso_scope": self.ui.iso_scope_radio,
"iso_type": self.ui.iso_type_radio
"follow": self.ui.follow_cb
})
# Fill form fields only on object create
self.to_form()
assert isinstance(self.ui, GerberObjectUI)
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
# Tools
self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
@ -235,54 +213,17 @@ class GerberObject(FlatCAMObj, Gerber):
self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
# set the model for the Area Exception comboboxes
self.ui.obj_combo.setModel(self.app.collection)
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.ui.obj_combo.is_last = True
self.ui.obj_combo.obj_type = {
_("Gerber"): "Gerber", _("Geometry"): "Geometry"
}[self.ui.type_obj_combo.get_value()]
self.on_type_obj_index_changed()
self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change)
# establish visibility for the GUI elements found in the slot function
self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type'])
# Show/Hide Advanced Options
if self.app.defaults["global_app_level"] == 'b':
self.ui.level.setText('<span style="color:green;"><b>%s</b></span>' % _('Basic'))
self.options['tool_type'] = 'circular'
self.ui.tool_type_label.hide()
self.ui.tool_type_radio.hide()
# override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1')
self.ui.tool_type_radio.set_value('circular')
self.ui.tipdialabel.hide()
self.ui.tipdia_spinner.hide()
self.ui.tipanglelabel.hide()
self.ui.tipangle_spinner.hide()
self.ui.cutzlabel.hide()
self.ui.cutz_spinner.hide()
self.ui.apertures_table_label.hide()
self.ui.aperture_table_visibility_cb.hide()
self.ui.milling_type_label.hide()
self.ui.milling_type_radio.hide()
self.ui.iso_type_label.hide()
self.ui.iso_type_radio.hide()
self.ui.follow_cb.hide()
self.ui.except_cb.setChecked(False)
self.ui.except_cb.hide()
else:
self.ui.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia)
self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia)
self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia)
if self.app.defaults["gerber_buffering"] == 'no':
self.ui.create_buffer_button.show()
@ -300,58 +241,6 @@ class GerberObject(FlatCAMObj, Gerber):
self.build_ui()
self.units_found = self.app.defaults['units']
def on_calculate_tooldia(self):
try:
tdia = float(self.ui.tipdia_spinner.get_value())
except Exception:
return
try:
dang = float(self.ui.tipangle_spinner.get_value())
except Exception:
return
try:
cutz = float(self.ui.cutz_spinner.get_value())
except Exception:
return
cutz *= -1
if cutz < 0:
cutz *= -1
half_tip_angle = dang / 2
tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle)))
self.ui.iso_tool_dia_entry.set_value(tool_diameter)
def on_type_obj_index_changed(self):
val = self.ui.type_obj_combo.get_value()
obj_type = {"Gerber": 0, "Geometry": 2}[val]
self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
self.ui.obj_combo.setCurrentIndex(0)
self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val]
def on_tool_type_change(self, state):
if state == 'circular':
self.ui.tipdialabel.hide()
self.ui.tipdia_spinner.hide()
self.ui.tipanglelabel.hide()
self.ui.tipangle_spinner.hide()
self.ui.cutzlabel.hide()
self.ui.cutz_spinner.hide()
self.ui.iso_tool_dia_entry.setDisabled(False)
# update the value in the self.iso_tool_dia_entry once this is selected
self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia'])
else:
self.ui.tipdialabel.show()
self.ui.tipdia_spinner.show()
self.ui.tipanglelabel.show()
self.ui.tipangle_spinner.show()
self.ui.cutzlabel.show()
self.ui.cutz_spinner.show()
self.ui.iso_tool_dia_entry.setDisabled(True)
# update the value in the self.iso_tool_dia_entry once this is selected
self.on_calculate_tooldia()
def build_ui(self):
FlatCAMObj.build_ui(self)
@ -562,521 +451,6 @@ class GerberObject(FlatCAMObj, Gerber):
self.app.app_obj.new_object("geometry", name, geo_init)
def on_iso_button_click(self, *args):
obj = self.app.collection.get_active()
self.iso_type = 2
if self.ui.iso_type_radio.get_value() == 'ext':
self.iso_type = 0
if self.ui.iso_type_radio.get_value() == 'int':
self.iso_type = 1
def worker_task(iso_obj, app_obj):
with self.app.proc_container.new(_("Isolating...")):
if self.ui.follow_cb.get_value() is True:
iso_obj.follow_geo()
# in the end toggle the visibility of the origin object so we can see the generated Geometry
iso_obj.ui.plot_cb.toggle()
else:
app_obj.defaults.report_usage("gerber_on_iso_button")
self.read_form()
iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
def follow_geo(self, outname=None):
"""
Creates a geometry object "following" the gerber paths.
:return: None
"""
# default_name = self.options["name"] + "_follow"
# follow_name = outname or default_name
if outname is None:
follow_name = self.options["name"] + "_follow"
else:
follow_name = outname
def follow_init(follow_obj, app):
# Propagate options
follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
follow_obj.solid_geometry = self.follow_geometry
# TODO: Do something if this is None. Offer changing name?
try:
self.app.app_obj.new_object("geometry", follow_name, follow_init)
except Exception as e:
return "Operation failed: %s" % str(e)
def isolate_handler(self, iso_type, iso_scope):
if iso_scope == 'all':
self.isolate(iso_type=iso_type)
else:
# disengage the grid snapping since it may be hard to click on polygons with grid snapping on
if self.app.ui.grid_snap_btn.isChecked():
self.grid_status_memory = True
self.app.ui.grid_snap_btn.trigger()
else:
self.grid_status_memory = False
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
else:
self.app.plotcanvas.graph_event_disconnect(self.app.mr)
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
def on_mouse_click_release(self, event):
if self.app.is_legacy is False:
event_pos = event.pos
right_button = 2
self.app.event_is_dragging = self.app.event_is_dragging
else:
event_pos = (event.xdata, event.ydata)
right_button = 3
self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
try:
x = float(event_pos[0])
y = float(event_pos[1])
except TypeError:
return
event_pos = (x, y)
curr_pos = self.app.plotcanvas.translate_coords(event_pos)
if self.app.grid_status():
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
else:
curr_pos = (curr_pos[0], curr_pos[1])
if event.button == 1:
clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
if self.app.selection_type is not None:
self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
self.app.selection_type = None
elif clicked_poly:
if clicked_poly not in self.poly_dict.values():
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = clicked_poly
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
_("Click to add next polygon or right click to start isolation."))
)
else:
try:
for k, v in list(self.poly_dict.items()):
if v == clicked_poly:
self.app.tool_shapes.remove(k)
self.poly_dict.pop(k)
break
except TypeError:
return
self.app.inform.emit(
'%s. %s' % (_("Removed polygon"),
_("Click to add/remove next polygon or right click to start isolation."))
)
self.app.tool_shapes.redraw()
else:
self.app.inform.emit(_("No polygon detected under click position."))
elif event.button == right_button and self.app.event_is_dragging is False:
# restore the Grid snapping if it was active before
if self.grid_status_memory is True:
self.app.ui.grid_snap_btn.trigger()
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
else:
self.app.plotcanvas.graph_event_disconnect(self.mr)
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
self.app.on_mouse_click_release_over_plot)
self.app.tool_shapes.clear(update=True)
if self.poly_dict:
poly_list = deepcopy(list(self.poly_dict.values()))
self.isolate(iso_type=self.iso_type, geometry=poly_list)
self.poly_dict.clear()
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
def selection_area_handler(self, start_pos, end_pos, sel_type):
"""
:param start_pos: mouse position when the selection LMB click was done
:param end_pos: mouse position when the left mouse button is released
:param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
:return:
"""
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
# delete previous selection shape
self.app.delete_selection_shape()
added_poly_count = 0
try:
for geo in self.solid_geometry:
if geo not in self.poly_dict.values():
if sel_type is True:
if geo.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
else:
if poly_selection.intersects(geo):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
except TypeError:
if self.solid_geometry not in self.poly_dict.values():
if sel_type is True:
if self.solid_geometry.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
else:
if poly_selection.intersects(self.solid_geometry):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
if added_poly_count > 0:
self.app.tool_shapes.redraw()
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"),
int(added_poly_count),
_("Click to add next polygon or right click to start isolation."))
)
else:
self.app.inform.emit(_("No polygon in selection."))
def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
milling_type=None, follow=None, plot=True):
"""
Creates an isolation routing geometry object in the project.
:param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
:param geometry: specific geometry to isolate
:param dia: Tool diameter
:param passes: Number of tool widths to cut
:param overlap: Overlap between passes in fraction of tool diameter
:param outname: Base name of the output object
:param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
:param milling_type: type of milling: conventional or climbing
:param follow: Boolean: if to generate a 'follow' geometry
:param plot: Boolean: if to plot the resulting geometry object
:return: None
"""
if geometry is None:
work_geo = self.follow_geometry if follow is True else self.solid_geometry
else:
work_geo = geometry
if dia is None:
dia = float(self.options["isotooldia"])
if passes is None:
passes = int(self.options["isopasses"])
if overlap is None:
overlap = float(self.options["isooverlap"])
overlap /= 100.0
combine = self.options["combine_passes"] if combine is None else bool(combine)
if milling_type is None:
milling_type = self.options["milling_type"]
if iso_type is None:
iso_t = 2
else:
iso_t = iso_type
base_name = self.options["name"]
if combine:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso"
elif self.iso_type == 1:
iso_name = base_name + "_int_iso"
else:
iso_name = base_name + "_iso"
else:
iso_name = outname
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
geo_obj.solid_geometry = []
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
else:
new_cutz = self.app.defaults['geometry_cutz']
new_vtipdia = self.app.defaults['geometry_vtipdia']
new_vtipangle = self.app.defaults['geometry_vtipangle']
tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": iso_name,
"plot": self.app.defaults['geometry_plot'],
"cutz": new_cutz,
"vtipdia": new_vtipdia,
"vtipangle": new_vtipangle,
"travelz": self.app.defaults['geometry_travelz'],
"feedrate": self.app.defaults['geometry_feedrate'],
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
"dwell": self.app.defaults['geometry_dwell'],
"dwelltime": self.app.defaults['geometry_dwelltime'],
"multidepth": self.app.defaults['geometry_multidepth'],
"ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'],
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
"startz": self.app.defaults['geometry_startz']
})
geo_obj.tools = {}
geo_obj.tools['1'] = {}
geo_obj.tools.update({
'1': {
'tooldia': float(self.options["isotooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': tool_type,
'data': default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
for nr_pass in range(passes):
iso_offset = dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * dia)
# if milling type is climb then the move is counter-clockwise around features
mill_dir = 1 if milling_type == 'cl' else 0
geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
follow=follow, nr_passes=nr_pass)
if geom == 'fail':
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry.append(geom)
# update the geometry in the tools
geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
if not isinstance(geo_obj.solid_geometry, list) and \
not isinstance(geo_obj.solid_geometry, MultiPolygon):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
if g:
break
else:
empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None)
else:
app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
# even if combine is checked, one pass is still single-geo
geo_obj.multigeo = True if passes > 1 else False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name?
self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
else:
for i in range(passes):
offset = dia * ((2 * i + 1) / 2.0000001) - (i * overlap * dia)
if passes > 1:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso" + str(i + 1)
elif self.iso_type == 1:
iso_name = base_name + "_int_iso" + str(i + 1)
else:
iso_name = base_name + "_iso" + str(i + 1)
else:
iso_name = outname
else:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso"
elif self.iso_type == 1:
iso_name = base_name + "_int_iso"
else:
iso_name = base_name + "_iso"
else:
iso_name = outname
def iso_init(geo_obj, fc_obj):
# Propagate options
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
if self.ui.tool_type_radio.get_value() == 'v':
geo_obj.tool_type = 'V'
else:
geo_obj.tool_type = 'C1'
# if milling type is climb then the move is counter-clockwise around features
mill_dir = 1 if milling_type == 'cl' else 0
geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
follow=follow,
nr_passes=i)
if geom == 'fail':
fc_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry = geom
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
# even if the resulting geometry is not multigeo we add the tools dict which will hold the data
# required to be transfered to the Geometry object
if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
else:
new_cutz = self.app.defaults['geometry_cutz']
new_vtipdia = self.app.defaults['geometry_vtipdia']
new_vtipangle = self.app.defaults['geometry_vtipangle']
tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": iso_name,
"plot": self.app.defaults['geometry_plot'],
"cutz": new_cutz,
"vtipdia": new_vtipdia,
"vtipangle": new_vtipangle,
"travelz": self.app.defaults['geometry_travelz'],
"feedrate": self.app.defaults['geometry_feedrate'],
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
"dwell": self.app.defaults['geometry_dwell'],
"dwelltime": self.app.defaults['geometry_dwelltime'],
"multidepth": self.app.defaults['geometry_multidepth'],
"ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'],
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
"startz": self.app.defaults['geometry_startz']
})
geo_obj.tools = {}
geo_obj.tools['1'] = {}
geo_obj.tools.update({
'1': {
'tooldia': float(self.options["isotooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': tool_type,
'data': default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
if not isinstance(geo_obj.solid_geometry, list):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
if g:
break
else:
empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None)
else:
fc_obj.inform.emit('[success] %s: %s' %
(_("Isolation geometry created"), geo_obj.options["name"]))
geo_obj.multigeo = False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name?
self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0):
# isolation_geometry produces an envelope that is going on the left of the geometry
# (the copper features). To leave the least amount of burrs on the features
@ -1117,65 +491,6 @@ class GerberObject(FlatCAMObj, Gerber):
return 'fail'
return geom
def area_subtraction(self, geo, subtractor_geo=None):
"""
Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
:param geo: target geometry from which to subtract
:param subtractor_geo: geometry that acts as subtractor
:return:
"""
new_geometry = []
target_geo = geo
if subtractor_geo:
sub_union = cascaded_union(subtractor_geo)
else:
name = self.ui.obj_combo.currentText()
subtractor_obj = self.app.collection.get_by_name(name)
sub_union = cascaded_union(subtractor_obj.solid_geometry)
try:
for geo_elem in target_geo:
if isinstance(geo_elem, Polygon):
for ring in self.poly2rings(geo_elem):
new_geo = ring.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, MultiPolygon):
for poly in geo_elem:
for ring in self.poly2rings(poly):
new_geo = ring.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, LineString):
new_geo = geo_elem.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, MultiLineString):
for line_elem in geo_elem:
new_geo = line_elem.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
except TypeError:
if isinstance(target_geo, Polygon):
for ring in self.poly2rings(target_geo):
new_geo = ring.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(target_geo, LineString):
new_geo = target_geo.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(target_geo, MultiLineString):
for line_elem in target_geo:
new_geo = line_elem.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
return new_geometry
def on_plot_cb_click(self, *args):
if self.muted_ui:
return

View File

@ -95,7 +95,7 @@ class ToolEtchCompensation(AppTool):
hlay_1 = QtWidgets.QHBoxLayout()
self.oz_entry = NumericalEvalEntry()
self.oz_entry = NumericalEvalEntry(border_color='#0069A9')
self.oz_entry.setPlaceholderText(_("Oz value"))
self.oz_to_um_entry = FCEntry()
self.oz_to_um_entry.setPlaceholderText(_("Microns value"))
@ -116,7 +116,7 @@ class ToolEtchCompensation(AppTool):
hlay_2 = QtWidgets.QHBoxLayout()
self.mils_entry = NumericalEvalEntry()
self.mils_entry = NumericalEvalEntry(border_color='#0069A9')
self.mils_entry.setPlaceholderText(_("Mils value"))
self.mils_to_um_entry = FCEntry()
self.mils_to_um_entry.setPlaceholderText(_("Microns value"))
@ -180,7 +180,7 @@ class ToolEtchCompensation(AppTool):
_("The ratio between depth etch and lateral etch .\n"
"Accepts real numbers and formulas using the operators: /,*,+,-,%")
)
self.factor_entry = NumericalEvalEntry()
self.factor_entry = NumericalEvalEntry(border_color='#0069A9')
self.factor_entry.setPlaceholderText(_("Real number or formula"))
self.factor_entry.setObjectName(_("Etch_factor"))

View File

@ -213,7 +213,7 @@ class ToolIsolation(AppTool, Gerber):
"- 'V-shape'\n"
"- Circular")
)
self.tool_type_radio.setObjectName(_("Tool Type"))
self.tool_type_radio.setObjectName("i_tool_type")
self.grid3.addWidget(self.tool_type_label, 2, 0)
self.grid3.addWidget(self.tool_type_radio, 2, 1)
@ -226,7 +226,7 @@ class ToolIsolation(AppTool, Gerber):
self.tipdia_entry.set_precision(self.decimals)
self.tipdia_entry.set_range(0.0000, 9999.9999)
self.tipdia_entry.setSingleStep(0.1)
self.tipdia_entry.setObjectName(_("V-Tip Dia"))
self.tipdia_entry.setObjectName("i_vtipdia")
self.grid3.addWidget(self.tipdialabel, 3, 0)
self.grid3.addWidget(self.tipdia_entry, 3, 1)
@ -240,7 +240,7 @@ class ToolIsolation(AppTool, Gerber):
self.tipangle_entry.set_precision(self.decimals)
self.tipangle_entry.set_range(0.0000, 180.0000)
self.tipangle_entry.setSingleStep(5)
self.tipangle_entry.setObjectName(_("V-Tip Angle"))
self.tipangle_entry.setObjectName("i_vtipangle")
self.grid3.addWidget(self.tipanglelabel, 4, 0)
self.grid3.addWidget(self.tipangle_entry, 4, 1)
@ -254,7 +254,7 @@ class ToolIsolation(AppTool, Gerber):
self.cutz_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.cutz_entry.set_precision(self.decimals)
self.cutz_entry.set_range(-99999.9999, 0.0000)
self.cutz_entry.setObjectName(_("Cut Z"))
self.cutz_entry.setObjectName("i_vcutz")
self.grid3.addWidget(cutzlabel, 5, 0)
self.grid3.addWidget(self.cutz_entry, 5, 1)
@ -269,7 +269,7 @@ class ToolIsolation(AppTool, Gerber):
self.addtool_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.addtool_entry.set_precision(self.decimals)
self.addtool_entry.set_range(0.000, 9999.9999)
self.addtool_entry.setObjectName(_("Tool Dia"))
self.addtool_entry.setObjectName("i_new_tooldia")
self.grid3.addWidget(self.addtool_entry_lbl, 6, 0)
self.grid3.addWidget(self.addtool_entry, 6, 1)
@ -402,7 +402,7 @@ class ToolIsolation(AppTool, Gerber):
self.iso_type_radio = RadioSet([{'label': _('Full'), 'value': 'full'},
{'label': _('Ext'), 'value': 'ext'},
{'label': _('Int'), 'value': 'int'}])
self.iso_type_radio.setObjectName("i_type")
self.iso_type_radio.setObjectName("i_iso_type")
self.grid3.addWidget(self.iso_type_label, 17, 0)
self.grid3.addWidget(self.iso_type_radio, 17, 1)
@ -432,8 +432,8 @@ class ToolIsolation(AppTool, Gerber):
self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
# Rest Machining
self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.rest_cb.setObjectName("i_rest_machining")
self.rest_cb = FCCheckBox('%s' % _("Rest"))
self.rest_cb.setObjectName("i_rest")
self.rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"
"Basically it will isolate outside PCB features,\n"
@ -677,21 +677,21 @@ class ToolIsolation(AppTool, Gerber):
self.tooldia = None
self.form_fields = {
"tools_iso_passes": self.passes_entry,
"tools_iso_overlap": self.iso_overlap_entry,
"tools_iso_milling_type": self.milling_type_radio,
"tools_iso_combine": self.combine_passes_cb,
"tools_iso_follow": self.follow_cb,
"tools_iso_isotype": self.iso_type_radio
"tools_iso_passes": self.passes_entry,
"tools_iso_overlap": self.iso_overlap_entry,
"tools_iso_milling_type": self.milling_type_radio,
"tools_iso_combine": self.combine_passes_cb,
"tools_iso_follow": self.follow_cb,
"tools_iso_isotype": self.iso_type_radio
}
self.name2option = {
"i_passes": "tools_iso_passes",
"i_overlap": "tools_iso_overlap",
"i_milling_type": "tools_iso_milling_type",
"i_combine": "tools_iso_combine",
"i_follow": "tools_iso_follow",
"i_type": "tools_iso_isotype"
"i_passes": "tools_iso_passes",
"i_overlap": "tools_iso_overlap",
"i_milling_type": "tools_iso_milling_type",
"i_combine": "tools_iso_combine",
"i_follow": "tools_iso_follow",
"i_iso_type": "tools_iso_isotype"
}
self.old_tool_dia = None
@ -715,7 +715,7 @@ class ToolIsolation(AppTool, Gerber):
self.type_excobj_radio.activated_custom.connect(self.on_type_excobj_index_changed)
self.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked)
self.addtool_from_db_btn.clicked.connect(self.on_ncc_tool_add_from_db_clicked)
self.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked)
self.generate_iso_button.clicked.connect(self.on_isolate_click)
self.reset_button.clicked.connect(self.set_tool_ui)
@ -731,21 +731,6 @@ class ToolIsolation(AppTool, Gerber):
"gerber": "Gerber", "geometry": "Geometry"
}[self.type_excobj_radio.get_value()]
def on_operation_change(self, val):
if val == 'iso':
self.milling_type_label.setEnabled(True)
self.milling_type_radio.setEnabled(True)
else:
self.milling_type_label.setEnabled(False)
self.milling_type_radio.setEnabled(False)
current_row = self.tools_table.currentRow()
try:
current_uid = int(self.tools_table.item(current_row, 3).text())
self.iso_tools[current_uid]['data']['tools_nccoperation'] = val
except AttributeError:
return
def on_row_selection_change(self):
self.blockSignals(True)
@ -782,7 +767,7 @@ class ToolIsolation(AppTool, Gerber):
form_value_storage = tooluid_value[key]
self.storage_to_form(form_value_storage)
except Exception as e:
log.debug("NonCopperClear ---> update_ui() " + str(e))
log.debug("ToolIsolation ---> update_ui() " + str(e))
else:
self.tool_data_label.setText(
"<b>%s: <font color='#0000FF'>%s</font></b>" % (_('Parameters for'), _("Multiple Tools"))
@ -797,7 +782,7 @@ class ToolIsolation(AppTool, Gerber):
try:
self.form_fields[form_key].set_value(dict_storage[form_key])
except Exception as e:
log.debug("NonCopperClear.storage_to_form() --> %s" % str(e))
log.debug("ToolIsolation.storage_to_form() --> %s" % str(e))
pass
def form_to_storage(self):
@ -831,7 +816,7 @@ class ToolIsolation(AppTool, Gerber):
def on_apply_param_to_all_clicked(self):
if self.tools_table.rowCount() == 0:
# there is no tool in tool table so we can't save the GUI elements values to storage
log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
log.debug("ToolIsolation.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.")
return
self.blockSignals(True)
@ -853,47 +838,7 @@ class ToolIsolation(AppTool, Gerber):
for tooluid_key, tooluid_val in self.iso_tools.items():
tooluid_val['data'] = deepcopy(temp_tool_data)
# store all the data associated with the row parameter to the self.tools storage
# tooldia_item = float(self.tools_table.item(row, 1).text())
# type_item = self.tools_table.cellWidget(row, 2).currentText()
# operation_type_item = self.tools_table.cellWidget(row, 4).currentText()
#
# nccoffset_item = self.ncc_choice_offset_cb.get_value()
# nccoffset_value_item = float(self.ncc_offset_spinner.get_value())
# this new dict will hold the actual useful data, another dict that is the value of key 'data'
# temp_tools = {}
# temp_dia = {}
# temp_data = {}
#
# for tooluid_key, tooluid_value in self.iso_tools.items():
# for key, value in tooluid_value.items():
# if key == 'data':
# # update the 'data' section
# for data_key in tooluid_value[key].keys():
# for form_key, form_value in self.form_fields.items():
# if form_key == data_key:
# temp_data[data_key] = form_value.get_value()
# # make sure we make a copy of the keys not in the form (we may use 'data' keys that are
# # updated from self.app.defaults
# if data_key not in self.form_fields:
# temp_data[data_key] = value[data_key]
# temp_dia[key] = deepcopy(temp_data)
# temp_data.clear()
#
# elif key == 'solid_geometry':
# temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry'])
# else:
# temp_dia[key] = deepcopy(value)
#
# temp_tools[tooluid_key] = deepcopy(temp_dia)
#
# self.iso_tools.clear()
# self.iso_tools = deepcopy(temp_tools)
# temp_tools.clear()
self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools."))
self.blockSignals(False)
def on_add_tool_by_key(self):
@ -979,14 +924,14 @@ class ToolIsolation(AppTool, Gerber):
self.iso_type_radio.set_value('full')
self.iso_type_radio.hide()
self.follow_cb.setChecked(False)
self.follow_cb.set_value(False)
self.follow_cb.hide()
self.follow_label.hide()
self.rest_cb.setChecked(False)
self.rest_cb.set_value(False)
self.rest_cb.hide()
self.except_cb.setChecked(False)
self.except_cb.set_value(False)
self.except_cb.hide()
self.select_combo.setCurrentIndex(0)
@ -995,7 +940,6 @@ class ToolIsolation(AppTool, Gerber):
else:
self.level.setText('<span style="color:red;"><b>%s</b></span>' % _('Advanced'))
# TODO remember to set the GUI elements to values from app.defaults dict
self.tool_type_radio.set_value(self.app.defaults["tools_iso_tool_type"])
self.tool_type_label.show()
self.tool_type_radio.show()
@ -1007,17 +951,17 @@ class ToolIsolation(AppTool, Gerber):
self.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"])
self.iso_type_radio.show()
self.follow_cb.setChecked(self.app.defaults["tools_iso_follow"])
self.follow_cb.set_value(self.app.defaults["tools_iso_follow"])
self.follow_cb.show()
self.follow_label.show()
self.rest_cb.setChecked(self.app.defaults["tools_iso_rest"])
self.rest_cb.set_value(self.app.defaults["tools_iso_rest"])
self.rest_cb.show()
self.except_cb.setChecked(self.app.defaults["tools_iso_isoexcept"])
self.except_cb.set_value(self.app.defaults["tools_iso_isoexcept"])
self.except_cb.show()
self.select_combo.setCurrentIndex(self.app.defaults["tools_iso_selection"])
self.select_combo.set_value(self.app.defaults["tools_iso_selection"])
self.select_combo.show()
self.select_label.show()
@ -1185,21 +1129,12 @@ class ToolIsolation(AppTool, Gerber):
tool_uid_item = QtWidgets.QTableWidgetItem(str(int(tooluid_key)))
# operation_type = FCComboBox()
# operation_type.addItems(['iso_op', 'clear_op'])
#
# # operation_type.setStyleSheet('background-color: rgb(255,255,255)')
# op_idx = operation_type.findText(tooluid_value['operation'])
# operation_type.setCurrentIndex(op_idx)
self.tools_table.setItem(row_no, 1, dia) # Diameter
self.tools_table.setCellWidget(row_no, 2, tool_type_item)
# ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ##
self.tools_table.setItem(row_no, 3, tool_uid_item) # Tool unique ID
# self.tools_table.setCellWidget(row_no, 4, operation_type)
# make the diameter column editable
for row in range(tool_id):
self.tools_table.item(row, 1).setFlags(
@ -1746,6 +1681,596 @@ class ToolIsolation(AppTool, Gerber):
isotooldia=self.iso_dia_list,
outname=self.o_name)
# ###########################################
# ###########################################
# ###########################################
# ###########################################
def on_iso_button_click(self, *args):
obj = self.app.collection.get_active()
self.iso_type = 2
if self.ui.iso_type_radio.get_value() == 'ext':
self.iso_type = 0
if self.ui.iso_type_radio.get_value() == 'int':
self.iso_type = 1
def worker_task(iso_obj, app_obj):
with self.app.proc_container.new(_("Isolating...")):
if self.ui.follow_cb.get_value() is True:
iso_obj.follow_geo()
# in the end toggle the visibility of the origin object so we can see the generated Geometry
iso_obj.ui.plot_cb.toggle()
else:
app_obj.defaults.report_usage("gerber_on_iso_button")
self.read_form()
iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single'
self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope)
self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]})
def follow_geo(self, outname=None):
"""
Creates a geometry object "following" the gerber paths.
:return: None
"""
# default_name = self.options["name"] + "_follow"
# follow_name = outname or default_name
if outname is None:
follow_name = self.options["name"] + "_follow"
else:
follow_name = outname
def follow_init(follow_obj, app):
# Propagate options
follow_obj.options["cnctooldia"] = str(self.options["isotooldia"])
follow_obj.solid_geometry = self.follow_geometry
# TODO: Do something if this is None. Offer changing name?
try:
self.app.app_obj.new_object("geometry", follow_name, follow_init)
except Exception as e:
return "Operation failed: %s" % str(e)
def isolate_handler(self, iso_type, iso_scope):
if iso_scope == 'all':
self.isolate(iso_type=iso_type)
else:
# disengage the grid snapping since it may be hard to click on polygons with grid snapping on
if self.app.ui.grid_snap_btn.isChecked():
self.grid_status_memory = True
self.app.ui.grid_snap_btn.trigger()
else:
self.grid_status_memory = False
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
else:
self.app.plotcanvas.graph_event_disconnect(self.app.mr)
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it."))
def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None,
milling_type=None, follow=None, plot=True):
"""
Creates an isolation routing geometry object in the project.
:param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both
:param geometry: specific geometry to isolate
:param dia: Tool diameter
:param passes: Number of tool widths to cut
:param overlap: Overlap between passes in fraction of tool diameter
:param outname: Base name of the output object
:param combine: Boolean: if to combine passes in one resulting object in case of multiple passes
:param milling_type: type of milling: conventional or climbing
:param follow: Boolean: if to generate a 'follow' geometry
:param plot: Boolean: if to plot the resulting geometry object
:return: None
"""
if geometry is None:
work_geo = self.follow_geometry if follow is True else self.solid_geometry
else:
work_geo = geometry
if dia is None:
dia = float(self.options["isotooldia"])
if passes is None:
passes = int(self.options["isopasses"])
if overlap is None:
overlap = float(self.options["isooverlap"])
overlap /= 100.0
combine = self.options["combine_passes"] if combine is None else bool(combine)
if milling_type is None:
milling_type = self.options["milling_type"]
if iso_type is None:
iso_t = 2
else:
iso_t = iso_type
base_name = self.options["name"]
if combine:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso"
elif self.iso_type == 1:
iso_name = base_name + "_int_iso"
else:
iso_name = base_name + "_iso"
else:
iso_name = outname
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper()
geo_obj.solid_geometry = []
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
else:
new_cutz = self.app.defaults['geometry_cutz']
new_vtipdia = self.app.defaults['geometry_vtipdia']
new_vtipangle = self.app.defaults['geometry_vtipangle']
tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": iso_name,
"plot": self.app.defaults['geometry_plot'],
"cutz": new_cutz,
"vtipdia": new_vtipdia,
"vtipangle": new_vtipangle,
"travelz": self.app.defaults['geometry_travelz'],
"feedrate": self.app.defaults['geometry_feedrate'],
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
"dwell": self.app.defaults['geometry_dwell'],
"dwelltime": self.app.defaults['geometry_dwelltime'],
"multidepth": self.app.defaults['geometry_multidepth'],
"ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'],
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
"startz": self.app.defaults['geometry_startz']
})
geo_obj.tools = {}
geo_obj.tools['1'] = {}
geo_obj.tools.update({
'1': {
'tooldia': float(self.options["isotooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': tool_type,
'data': default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
for nr_pass in range(passes):
iso_offset = dia * ((2 * nr_pass + 1) / 2.0000001) - (nr_pass * overlap * dia)
# if milling type is climb then the move is counter-clockwise around features
mill_dir = 1 if milling_type == 'cl' else 0
geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
follow=follow, nr_passes=nr_pass)
if geom == 'fail':
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry.append(geom)
# update the geometry in the tools
geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
if not isinstance(geo_obj.solid_geometry, list) and \
not isinstance(geo_obj.solid_geometry, MultiPolygon):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
if g:
break
else:
empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None)
else:
app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"]))
# even if combine is checked, one pass is still single-geo
geo_obj.multigeo = True if passes > 1 else False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name?
self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
else:
for i in range(passes):
offset = dia * ((2 * i + 1) / 2.0000001) - (i * overlap * dia)
if passes > 1:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso" + str(i + 1)
elif self.iso_type == 1:
iso_name = base_name + "_int_iso" + str(i + 1)
else:
iso_name = base_name + "_iso" + str(i + 1)
else:
iso_name = outname
else:
if outname is None:
if self.iso_type == 0:
iso_name = base_name + "_ext_iso"
elif self.iso_type == 1:
iso_name = base_name + "_int_iso"
else:
iso_name = base_name + "_iso"
else:
iso_name = outname
def iso_init(geo_obj, fc_obj):
# Propagate options
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
if self.ui.tool_type_radio.get_value() == 'v':
geo_obj.tool_type = 'V'
else:
geo_obj.tool_type = 'C1'
# if milling type is climb then the move is counter-clockwise around features
mill_dir = 1 if milling_type == 'cl' else 0
geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
follow=follow,
nr_passes=i)
if geom == 'fail':
fc_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
return 'fail'
geo_obj.solid_geometry = geom
# transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI
# even if the resulting geometry is not multigeo we add the tools dict which will hold the data
# required to be transfered to the Geometry object
if self.ui.tool_type_radio.get_value() == 'v':
new_cutz = self.ui.cutz_spinner.get_value()
new_vtipdia = self.ui.tipdia_spinner.get_value()
new_vtipangle = self.ui.tipangle_spinner.get_value()
tool_type = 'V'
else:
new_cutz = self.app.defaults['geometry_cutz']
new_vtipdia = self.app.defaults['geometry_vtipdia']
new_vtipangle = self.app.defaults['geometry_vtipangle']
tool_type = 'C1'
# store here the default data for Geometry Data
default_data = {}
default_data.update({
"name": iso_name,
"plot": self.app.defaults['geometry_plot'],
"cutz": new_cutz,
"vtipdia": new_vtipdia,
"vtipangle": new_vtipangle,
"travelz": self.app.defaults['geometry_travelz'],
"feedrate": self.app.defaults['geometry_feedrate'],
"feedrate_z": self.app.defaults['geometry_feedrate_z'],
"feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'],
"dwell": self.app.defaults['geometry_dwell'],
"dwelltime": self.app.defaults['geometry_dwelltime'],
"multidepth": self.app.defaults['geometry_multidepth'],
"ppname_g": self.app.defaults['geometry_ppname_g'],
"depthperpass": self.app.defaults['geometry_depthperpass'],
"extracut": self.app.defaults['geometry_extracut'],
"extracut_length": self.app.defaults['geometry_extracut_length'],
"toolchange": self.app.defaults['geometry_toolchange'],
"toolchangez": self.app.defaults['geometry_toolchangez'],
"endz": self.app.defaults['geometry_endz'],
"spindlespeed": self.app.defaults['geometry_spindlespeed'],
"toolchangexy": self.app.defaults['geometry_toolchangexy'],
"startz": self.app.defaults['geometry_startz']
})
geo_obj.tools = {}
geo_obj.tools['1'] = {}
geo_obj.tools.update({
'1': {
'tooldia': float(self.options["isotooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': tool_type,
'data': default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
# proceed with object creation, if there are empty and the number of them is the length
# of the list then we have an empty solid_geometry which should raise a Custom Exception
empty_cnt = 0
if not isinstance(geo_obj.solid_geometry, list):
geo_obj.solid_geometry = [geo_obj.solid_geometry]
for g in geo_obj.solid_geometry:
if g:
break
else:
empty_cnt += 1
if empty_cnt == len(geo_obj.solid_geometry):
raise ValidationError("Empty Geometry", None)
else:
fc_obj.inform.emit('[success] %s: %s' %
(_("Isolation geometry created"), geo_obj.options["name"]))
geo_obj.multigeo = False
# ############################################################
# ########## AREA SUBTRACTION ################################
# ############################################################
if self.ui.except_cb.get_value():
self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo"))
geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry)
# TODO: Do something if this is None. Offer changing name?
self.app.app_obj.new_object("geometry", iso_name, iso_init, plot=plot)
def area_subtraction(self, geo, subtractor_geo=None):
"""
Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo
:param geo: target geometry from which to subtract
:param subtractor_geo: geometry that acts as subtractor
:return:
"""
new_geometry = []
target_geo = geo
if subtractor_geo:
sub_union = cascaded_union(subtractor_geo)
else:
name = self.ui.obj_combo.currentText()
subtractor_obj = self.app.collection.get_by_name(name)
sub_union = cascaded_union(subtractor_obj.solid_geometry)
try:
for geo_elem in target_geo:
if isinstance(geo_elem, Polygon):
for ring in self.poly2rings(geo_elem):
new_geo = ring.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, MultiPolygon):
for poly in geo_elem:
for ring in self.poly2rings(poly):
new_geo = ring.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, LineString):
new_geo = geo_elem.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(geo_elem, MultiLineString):
for line_elem in geo_elem:
new_geo = line_elem.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
except TypeError:
if isinstance(target_geo, Polygon):
for ring in self.poly2rings(target_geo):
new_geo = ring.difference(sub_union)
if new_geo:
if not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(target_geo, LineString):
new_geo = target_geo.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
elif isinstance(target_geo, MultiLineString):
for line_elem in target_geo:
new_geo = line_elem.difference(sub_union)
if new_geo and not new_geo.is_empty:
new_geometry.append(new_geo)
return new_geometry
def on_mouse_click_release(self, event):
if self.app.is_legacy is False:
event_pos = event.pos
right_button = 2
self.app.event_is_dragging = self.app.event_is_dragging
else:
event_pos = (event.xdata, event.ydata)
right_button = 3
self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
try:
x = float(event_pos[0])
y = float(event_pos[1])
except TypeError:
return
event_pos = (x, y)
curr_pos = self.app.plotcanvas.translate_coords(event_pos)
if self.app.grid_status():
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
else:
curr_pos = (curr_pos[0], curr_pos[1])
if event.button == 1:
clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]))
if self.app.selection_type is not None:
self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type)
self.app.selection_type = None
elif clicked_poly:
if clicked_poly not in self.poly_dict.values():
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults['global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = clicked_poly
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)),
_("Click to add next polygon or right click to start isolation."))
)
else:
try:
for k, v in list(self.poly_dict.items()):
if v == clicked_poly:
self.app.tool_shapes.remove(k)
self.poly_dict.pop(k)
break
except TypeError:
return
self.app.inform.emit(
'%s. %s' % (_("Removed polygon"),
_("Click to add/remove next polygon or right click to start isolation."))
)
self.app.tool_shapes.redraw()
else:
self.app.inform.emit(_("No polygon detected under click position."))
elif event.button == right_button and self.app.event_is_dragging is False:
# restore the Grid snapping if it was active before
if self.grid_status_memory is True:
self.app.ui.grid_snap_btn.trigger()
if self.app.is_legacy is False:
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
else:
self.app.plotcanvas.graph_event_disconnect(self.mr)
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
self.app.on_mouse_click_release_over_plot)
self.app.tool_shapes.clear(update=True)
if self.poly_dict:
poly_list = deepcopy(list(self.poly_dict.values()))
self.isolate(iso_type=self.iso_type, geometry=poly_list)
self.poly_dict.clear()
else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting."))
def selection_area_handler(self, start_pos, end_pos, sel_type):
"""
:param start_pos: mouse position when the selection LMB click was done
:param end_pos: mouse position when the left mouse button is released
:param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection
:return:
"""
poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
# delete previous selection shape
self.app.delete_selection_shape()
added_poly_count = 0
try:
for geo in self.solid_geometry:
if geo not in self.poly_dict.values():
if sel_type is True:
if geo.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
else:
if poly_selection.intersects(geo):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=geo,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = geo
added_poly_count += 1
except TypeError:
if self.solid_geometry not in self.poly_dict.values():
if sel_type is True:
if self.solid_geometry.within(poly_selection):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
else:
if poly_selection.intersects(self.solid_geometry):
shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0,
shape=self.solid_geometry,
color=self.app.defaults['global_sel_draw_color'] + 'AF',
face_color=self.app.defaults[
'global_sel_draw_color'] + 'AF',
visible=True)
self.poly_dict[shape_id] = self.solid_geometry
added_poly_count += 1
if added_poly_count > 0:
self.app.tool_shapes.redraw()
self.app.inform.emit(
'%s: %d. %s' % (_("Added polygon"),
int(added_poly_count),
_("Click to add next polygon or right click to start isolation."))
)
else:
self.app.inform.emit(_("No polygon in selection."))
# ###########################################
# ###########################################
# ###########################################
# ###########################################
# To be called after clicking on the plot.
def on_mouse_release(self, event):
if self.app.is_legacy is False:
@ -2802,14 +3327,14 @@ class ToolIsolation(AppTool, Gerber):
return 'fail'
return geom
def on_ncc_tool_add_from_db_executed(self, tool):
def on_tool_add_from_db_executed(self, tool):
"""
Here add the tool from DB in the selected geometry object
:return:
"""
tool_from_db = deepcopy(tool)
res = self.on_ncc_tool_from_db_inserted(tool=tool_from_db)
res = self.on_tool_from_db_inserted(tool=tool_from_db)
for idx in range(self.app.ui.plot_tab_area.count()):
if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"):
@ -2828,7 +3353,7 @@ class ToolIsolation(AppTool, Gerber):
self.tools_table.selectRow(row)
self.on_row_selection_change()
def on_ncc_tool_from_db_inserted(self, tool):
def on_tool_from_db_inserted(self, tool):
"""
Called from the Tools DB object through a App method when adding a tool from Tools Database
:param tool: a dict with the tool data
@ -2887,7 +3412,7 @@ class ToolIsolation(AppTool, Gerber):
# if self.tools_table.rowCount() != 0:
# self.param_frame.setDisabled(False)
def on_ncc_tool_add_from_db_clicked(self):
def on_tool_add_from_db_clicked(self):
"""
Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object
and display the Tools Database tab in the form needed for the Tool adding
@ -2917,7 +3442,7 @@ class ToolIsolation(AppTool, Gerber):
self.cursor_pos = None
self.mouse_is_dragging = False
prog_plot = True if self.app.defaults["tools_ncc_plotting"] == 'progressive' else False
prog_plot = True if self.app.defaults["tools_iso_plotting"] == 'progressive' else False
if prog_plot:
self.temp_shapes.clear(update=True)

View File

@ -509,7 +509,7 @@ class NonCopperClear(AppTool, Gerber):
self.grid3.addWidget(self.gen_param_label, 24, 0, 1, 2)
# Rest Machining
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.ncc_rest_cb = FCCheckBox('%s' % _("Rest"))
self.ncc_rest_cb.setObjectName("n_rest_machining")
self.ncc_rest_cb.setToolTip(

View File

@ -438,7 +438,7 @@ class ToolPaint(AppTool, Gerber):
)
grid4.addWidget(self.gen_param_label, 15, 0, 1, 2)
self.rest_cb = FCCheckBox('%s' % _("Rest Machining"))
self.rest_cb = FCCheckBox('%s' % _("Rest"))
self.rest_cb.setObjectName('p_rest_machining')
self.rest_cb.setToolTip(
_("If checked, use 'rest machining'.\n"

View File

@ -14,6 +14,10 @@ CHANGELOG for FlatCAM beta
- if there is a Gerber object selected then in Isolation Tool the Gerber object combobox will show that object name as current
- made the Project Tree items not editable by clicking on selected Tree items (the object rename can still be done in the Selected tab)
- working on Isolation Tool: added a Preferences section in Edit -> Preferences and updated their usage within the Isolation tool
- fixed milling drills not plotting the resulting Geometry object
- all tuple entries in the Preferences UI are now protected against letter entry
- all entries in the Preferences UI that have numerical entry are protected now against letters
- cleaned the Preferences UI in the Gerber area
25.05.2020

View File

@ -4374,7 +4374,7 @@ class CNCjob(Geometry):
self.gcode += self.create_gcode_multi_pass(geo, current_tooldia, extracut, self.extracut_length,
tolerance,
z_move=z_move,_postproc=p,
z_move=z_move, postproc=p,
old_point=current_pt)
# calculate the travel distance

View File

@ -395,11 +395,12 @@ class FlatCAMDefaults:
"tools_iso_follow": False,
"tools_iso_isotype": "full",
"tools_iso_rest": False,
"tools_iso_rest": False,
"tools_iso_combine_passes": False,
"tools_iso_isoexcept": False,
"tools_iso_selection": _("All"),
"tools_iso_area_shape": "square",
"tools_iso_isoexcept": False,
"tools_iso_selection": _("All"),
"tools_iso_area_shape": "square",
"tools_iso_plotting": 'normal',
# NCC Tool
"tools_ncctools": "1.0, 0.5",
@ -415,13 +416,13 @@ class FlatCAMDefaults:
"tools_ncc_offset_value": 0.0000,
"tools_nccref": _('Itself'),
"tools_ncc_area_shape": "square",
"tools_ncc_plotting": 'normal',
"tools_nccmilling_type": 'cl',
"tools_ncctool_type": 'C1',
"tools_ncccutz": -0.05,
"tools_ncctipdia": 0.1,
"tools_ncctipangle": 30,
"tools_nccnewdia": 0.1,
"tools_ncc_plotting": 'normal',
# Cutout Tool
"tools_cutouttooldia": 2.4,