- in Tools: Film, Image, InvertGerber, Optimal, PcbWizard - moved the Tool UI in its own class

This commit is contained in:
Marius Stanciu
2020-08-27 21:37:00 +03:00
parent b3a22f3d0a
commit c45444a772
6 changed files with 823 additions and 716 deletions

View File

@@ -16,6 +16,7 @@ CHANGELOG for FlatCAM beta
- in Tool Cutout: modified the UI in preparation for adding the Mouse Bites feature - in Tool Cutout: modified the UI in preparation for adding the Mouse Bites feature
- Turkish translation strings were updated by the translator, Mehmet Kaya - Turkish translation strings were updated by the translator, Mehmet Kaya
- Film Tool - moved the Tool UI in its own class - Film Tool - moved the Tool UI in its own class
- in Tools: Film, Image, InvertGerber, Optimal, PcbWizard - moved the Tool UI in its own class
26.08.2020 26.08.2020

View File

@@ -9,7 +9,7 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from appTool import AppTool from appTool import AppTool
from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \ from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
OptionalHideInputSection, FCComboBox, FCFileSaveDialog OptionalHideInputSection, FCComboBox, FCFileSaveDialog, FCButton, FCLabel
from copy import deepcopy from copy import deepcopy
import logging import logging
@@ -759,7 +759,7 @@ class FilmUI:
self.layout = layout self.layout = layout
# ## Title # ## Title
title_label = QtWidgets.QLabel("%s" % self.toolName) title_label = FCLabel("%s" % self.toolName)
title_label.setStyleSheet(""" title_label.setStyleSheet("""
QLabel QLabel
{ {
@@ -768,7 +768,7 @@ class FilmUI:
} }
""") """)
self.layout.addWidget(title_label) self.layout.addWidget(title_label)
self.layout.addWidget(QtWidgets.QLabel("")) self.layout.addWidget(FCLabel(""))
# Form Layout # Form Layout
grid0 = QtWidgets.QGridLayout() grid0 = QtWidgets.QGridLayout()
@@ -781,7 +781,7 @@ class FilmUI:
self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'}, self.tf_type_obj_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
{'label': _('Geometry'), 'value': 'geo'}]) {'label': _('Geometry'), 'value': 'geo'}])
self.tf_type_obj_combo_label = QtWidgets.QLabel('<b>%s</b>:' % _("Object")) self.tf_type_obj_combo_label = FCLabel('<b>%s</b>:' % _("Object"))
self.tf_type_obj_combo_label.setToolTip( self.tf_type_obj_combo_label.setToolTip(
_("Specify the type of object for which to create the film.\n" _("Specify the type of object for which to create the film.\n"
"The object can be of type: Gerber or Geometry.\n" "The object can be of type: Gerber or Geometry.\n"
@@ -804,7 +804,7 @@ class FilmUI:
self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'}, self.tf_type_box_combo = RadioSet([{'label': _('Gerber'), 'value': 'grb'},
{'label': _('Geometry'), 'value': 'geo'}]) {'label': _('Geometry'), 'value': 'geo'}])
self.tf_type_box_combo_label = QtWidgets.QLabel(_("Box Type:")) self.tf_type_box_combo_label = FCLabel(_("Box Type:"))
self.tf_type_box_combo_label.setToolTip( self.tf_type_box_combo_label.setToolTip(
_("Specify the type of object to be used as an container for\n" _("Specify the type of object to be used as an container for\n"
"film creation. It can be: Gerber or Geometry type." "film creation. It can be: Gerber or Geometry type."
@@ -827,7 +827,7 @@ class FilmUI:
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 4, 0, 1, 2) grid0.addWidget(separator_line, 4, 0, 1, 2)
self.film_adj_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Adjustments")) self.film_adj_label = FCLabel('<b>%s</b>' % _("Film Adjustments"))
self.film_adj_label.setToolTip( self.film_adj_label.setToolTip(
_("Sometime the printers will distort the print shape, especially the Laser types.\n" _("Sometime the printers will distort the print shape, especially the Laser types.\n"
"This section provide the tools to compensate for the print distortions.") "This section provide the tools to compensate for the print distortions.")
@@ -848,7 +848,7 @@ class FilmUI:
) )
grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2) grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor")) self.film_scalex_label = FCLabel('%s:' % _("X factor"))
self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message) self.film_scalex_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.film_scalex_entry.set_range(-999.9999, 999.9999) self.film_scalex_entry.set_range(-999.9999, 999.9999)
self.film_scalex_entry.set_precision(self.decimals) self.film_scalex_entry.set_precision(self.decimals)
@@ -857,7 +857,7 @@ class FilmUI:
grid0.addWidget(self.film_scalex_label, 7, 0) grid0.addWidget(self.film_scalex_label, 7, 0)
grid0.addWidget(self.film_scalex_entry, 7, 1) grid0.addWidget(self.film_scalex_entry, 7, 1)
self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor")) self.film_scaley_label = FCLabel('%s:' % _("Y factor"))
self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message) self.film_scaley_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.film_scaley_entry.set_range(-999.9999, 999.9999) self.film_scaley_entry.set_range(-999.9999, 999.9999)
self.film_scaley_entry.set_precision(self.decimals) self.film_scaley_entry.set_precision(self.decimals)
@@ -892,7 +892,7 @@ class FilmUI:
) )
grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2) grid0.addWidget(self.film_skew_cb, 10, 0, 1, 2)
self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle")) self.film_skewx_label = FCLabel('%s:' % _("X angle"))
self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message) self.film_skewx_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.film_skewx_entry.set_range(-999.9999, 999.9999) self.film_skewx_entry.set_range(-999.9999, 999.9999)
self.film_skewx_entry.set_precision(self.decimals) self.film_skewx_entry.set_precision(self.decimals)
@@ -901,7 +901,7 @@ class FilmUI:
grid0.addWidget(self.film_skewx_label, 11, 0) grid0.addWidget(self.film_skewx_label, 11, 0)
grid0.addWidget(self.film_skewx_entry, 11, 1) grid0.addWidget(self.film_skewx_entry, 11, 1)
self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle")) self.film_skewy_label = FCLabel('%s:' % _("Y angle"))
self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message) self.film_skewy_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.film_skewy_entry.set_range(-999.9999, 999.9999) self.film_skewy_entry.set_range(-999.9999, 999.9999)
self.film_skewy_entry.set_precision(self.decimals) self.film_skewy_entry.set_precision(self.decimals)
@@ -910,7 +910,7 @@ class FilmUI:
grid0.addWidget(self.film_skewy_label, 12, 0) grid0.addWidget(self.film_skewy_label, 12, 0)
grid0.addWidget(self.film_skewy_entry, 12, 1) grid0.addWidget(self.film_skewy_entry, 12, 1)
self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference")) self.film_skew_ref_label = FCLabel('%s:' % _("Reference"))
self.film_skew_ref_label.setToolTip( self.film_skew_ref_label.setToolTip(
_("The reference point to be used as origin for the skew.\n" _("The reference point to be used as origin for the skew.\n"
"It can be one of the four points of the geometry bounding box.") "It can be one of the four points of the geometry bounding box.")
@@ -957,7 +957,7 @@ class FilmUI:
{'label': _('Y'), 'value': 'y'}, {'label': _('Y'), 'value': 'y'},
{'label': _('Both'), 'value': 'both'}], {'label': _('Both'), 'value': 'both'}],
stretch=False) stretch=False)
self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis")) self.film_mirror_axis_label = FCLabel('%s:' % _("Mirror axis"))
grid0.addWidget(self.film_mirror_axis_label, 16, 0) grid0.addWidget(self.film_mirror_axis_label, 16, 0)
grid0.addWidget(self.film_mirror_axis, 16, 1) grid0.addWidget(self.film_mirror_axis, 16, 1)
@@ -973,7 +973,7 @@ class FilmUI:
separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken) separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line2, 17, 0, 1, 2) grid0.addWidget(separator_line2, 17, 0, 1, 2)
self.film_param_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Parameters")) self.film_param_label = FCLabel('<b>%s</b>' % _("Film Parameters"))
grid0.addWidget(self.film_param_label, 18, 0, 1, 2) grid0.addWidget(self.film_param_label, 18, 0, 1, 2)
@@ -983,7 +983,7 @@ class FilmUI:
self.film_scale_stroke_entry.setSingleStep(0.01) self.film_scale_stroke_entry.setSingleStep(0.01)
self.film_scale_stroke_entry.set_precision(self.decimals) self.film_scale_stroke_entry.set_precision(self.decimals)
self.film_scale_stroke_label = QtWidgets.QLabel('%s:' % _("Scale Stroke")) self.film_scale_stroke_label = FCLabel('%s:' % _("Scale Stroke"))
self.film_scale_stroke_label.setToolTip( self.film_scale_stroke_label.setToolTip(
_("Scale the line stroke thickness of each feature in the SVG file.\n" _("Scale the line stroke thickness of each feature in the SVG file.\n"
"It means that the line that envelope each SVG feature will be thicker or thinner,\n" "It means that the line that envelope each SVG feature will be thicker or thinner,\n"
@@ -996,7 +996,7 @@ class FilmUI:
self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'}, self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
{'label': _('Negative'), 'value': 'neg'}], {'label': _('Negative'), 'value': 'neg'}],
stretch=False) stretch=False)
self.film_type_label = QtWidgets.QLabel(_("Film Type:")) self.film_type_label = FCLabel(_("Film Type:"))
self.film_type_label.setToolTip( self.film_type_label.setToolTip(
_("Generate a Positive black film or a Negative film.\n" _("Generate a Positive black film or a Negative film.\n"
"Positive means that it will print the features\n" "Positive means that it will print the features\n"
@@ -1014,7 +1014,7 @@ class FilmUI:
self.boundary_entry.setSingleStep(0.01) self.boundary_entry.setSingleStep(0.01)
self.boundary_entry.set_precision(self.decimals) self.boundary_entry.set_precision(self.decimals)
self.boundary_label = QtWidgets.QLabel('%s:' % _("Border")) self.boundary_label = FCLabel('%s:' % _("Border"))
self.boundary_label.setToolTip( self.boundary_label.setToolTip(
_("Specify a border around the object.\n" _("Specify a border around the object.\n"
"Only for negative film.\n" "Only for negative film.\n"
@@ -1051,7 +1051,7 @@ class FilmUI:
self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame]) self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
self.source_label = QtWidgets.QLabel('%s:' % _("Source")) self.source_label = FCLabel('%s:' % _("Source"))
self.source_label.setToolTip( self.source_label.setToolTip(
_("The punch hole source can be:\n" _("The punch hole source can be:\n"
"- Excellon -> an Excellon holes center will serve as reference.\n" "- Excellon -> an Excellon holes center will serve as reference.\n"
@@ -1063,7 +1063,7 @@ class FilmUI:
punch_grid.addWidget(self.source_label, 0, 0) punch_grid.addWidget(self.source_label, 0, 0)
punch_grid.addWidget(self.source_punch, 0, 1) punch_grid.addWidget(self.source_punch, 0, 1)
self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj")) self.exc_label = FCLabel('%s:' % _("Excellon Obj"))
self.exc_label.setToolTip( self.exc_label.setToolTip(
_("Remove the geometry of Excellon from the Film to create the holes in pads.") _("Remove the geometry of Excellon from the Film to create the holes in pads.")
) )
@@ -1079,7 +1079,7 @@ class FilmUI:
self.exc_label.hide() self.exc_label.hide()
self.exc_combo.hide() self.exc_combo.hide()
self.punch_size_label = QtWidgets.QLabel('%s:' % _("Punch Size")) self.punch_size_label = FCLabel('%s:' % _("Punch Size"))
self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads.")) self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
self.punch_size_spinner = FCDoubleSpinner(callback=self.confirmation_message) self.punch_size_spinner = FCDoubleSpinner(callback=self.confirmation_message)
self.punch_size_spinner.set_range(0, 999.9999) self.punch_size_spinner.set_range(0, 999.9999)
@@ -1108,7 +1108,7 @@ class FilmUI:
{'label': _('PDF'), 'value': 'pdf'} {'label': _('PDF'), 'value': 'pdf'}
], stretch=False) ], stretch=False)
self.file_type_label = QtWidgets.QLabel(_("Film Type:")) self.file_type_label = FCLabel(_("Film Type:"))
self.file_type_label.setToolTip( self.file_type_label.setToolTip(
_("The file type of the saved film. Can be:\n" _("The file type of the saved film. Can be:\n"
"- 'SVG' -> open-source vectorial format\n" "- 'SVG' -> open-source vectorial format\n"
@@ -1119,7 +1119,7 @@ class FilmUI:
grid1.addWidget(self.file_type_radio, 1, 1) grid1.addWidget(self.file_type_radio, 1, 1)
# Page orientation # Page orientation
self.orientation_label = QtWidgets.QLabel('%s:' % _("Page Orientation")) self.orientation_label = FCLabel('%s:' % _("Page Orientation"))
self.orientation_label.setToolTip(_("Can be:\n" self.orientation_label.setToolTip(_("Can be:\n"
"- Portrait\n" "- Portrait\n"
"- Landscape")) "- Landscape"))
@@ -1132,7 +1132,7 @@ class FilmUI:
grid1.addWidget(self.orientation_radio, 2, 1) grid1.addWidget(self.orientation_radio, 2, 1)
# Page Size # Page Size
self.pagesize_label = QtWidgets.QLabel('%s:' % _("Page Size")) self.pagesize_label = FCLabel('%s:' % _("Page Size"))
self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes.")) self.pagesize_label.setToolTip(_("A selection of standard ISO 216 page sizes."))
self.pagesize_combo = FCComboBox() self.pagesize_combo = FCComboBox()
@@ -1200,7 +1200,8 @@ class FilmUI:
self.on_film_type(val='hide') self.on_film_type(val='hide')
# Buttons # Buttons
self.film_object_button = QtWidgets.QPushButton(_("Save Film")) self.film_object_button = FCButton(_("Save Film"))
self.film_object_button.setIcon(QtGui.QIcon(self.app.resource_location + '/save_as.png'))
self.film_object_button.setToolTip( self.film_object_button.setToolTip(
_("Create a Film for the selected object, within\n" _("Create a Film for the selected object, within\n"
"the specified box. Does not create a new \n " "the specified box. Does not create a new \n "
@@ -1218,7 +1219,7 @@ class FilmUI:
self.layout.addStretch() self.layout.addStretch()
# ## Reset Tool # ## Reset Tool
self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) self.reset_button = FCButton(_("Reset Tool"))
self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
self.reset_button.setToolTip( self.reset_button.setToolTip(
_("Will reset the tool parameters.") _("Will reset the tool parameters.")

View File

@@ -21,16 +21,158 @@ if '_' not in builtins.__dict__:
class ToolImage(AppTool): class ToolImage(AppTool):
toolName = _("Image as Object")
def __init__(self, app): def __init__(self, app):
AppTool.__init__(self, app) AppTool.__init__(self, app)
self.app = app self.app = app
self.decimals = self.app.decimals self.decimals = self.app.decimals
# Title # #############################################################################
title_label = QtWidgets.QLabel("%s" % _('Image to PCB')) # ######################### Tool GUI ##########################################
# #############################################################################
self.ui = ImageUI(layout=self.layout, app=self.app)
self.toolName = self.ui.toolName
# ## Signals
self.ui.import_button.clicked.connect(self.on_file_importimage)
self.ui.image_type.activated_custom.connect(self.ui.on_image_type)
def run(self, toggle=True):
self.app.defaults.report_usage("ToolImage()")
if toggle:
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
else:
try:
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
# if tab is populated with the tool but it does not have the focus, focus on it
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
# focus on Tool Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
else:
self.app.ui.splitter.setSizes([0, 1])
except AttributeError:
pass
else:
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
AppTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Image Tool"))
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, **kwargs)
def set_tool_ui(self):
# ## Initialize form
self.ui.dpi_entry.set_value(96)
self.ui.image_type.set_value('black')
self.ui.mask_bw_entry.set_value(250)
self.ui.mask_r_entry.set_value(250)
self.ui.mask_g_entry.set_value(250)
self.ui.mask_b_entry.set_value(250)
def on_file_importimage(self):
"""
Callback for menu item File->Import IMAGE.
:param type_of_obj: to import the IMAGE as Geometry or as Gerber
:type type_of_obj: str
:return: None
"""
mask = []
self.app.log.debug("on_file_importimage()")
_filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \
"Bitmap File (*.BMP);;" \
"PNG File (*.PNG);;" \
"Jpeg File (*.JPG);;" \
"All Files (*.*)"
try:
filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"),
directory=self.app.get_last_folder(), filter=_filter)
except TypeError:
filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"), filter=filter)
filename = str(filename)
type_obj = self.ui.tf_type_obj_combo.get_value()
dpi = self.ui.dpi_entry.get_value()
mode = self.ui.image_type.get_value()
mask = [
self.ui.mask_bw_entry.get_value(),
self.ui.mask_r_entry.get_value(),
self.ui.mask_g_entry.get_value(),
self.ui.mask_b_entry.get_value()
]
if filename == "":
self.app.inform.emit(_("Cancelled."))
else:
self.app.worker_task.emit({'fcn': self.import_image,
'params': [filename, type_obj, dpi, mode, mask]})
def import_image(self, filename, o_type=_("Gerber"), dpi=96, mode='black', mask=None, outname=None):
"""
Adds a new Geometry Object to the projects and populates
it with shapes extracted from the SVG file.
:param filename: Path to the SVG file.
:param o_type: type of FlatCAM objeect
:param dpi: dot per inch
:param mode: black or color
:param mask: dictate the level of detail
:param outname: name for the resulting file
:return:
"""
self.app.defaults.report_usage("import_image()")
if mask is None:
mask = [250, 250, 250, 250]
if o_type is None or o_type == _("Geometry"):
obj_type = "geometry"
elif o_type == _("Gerber"):
obj_type = "gerber"
else:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_("Not supported type is picked as parameter. "
"Only Geometry and Gerber are supported"))
return
def obj_init(geo_obj, app_obj):
geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask)
geo_obj.multigeo = False
with self.app.proc_container.new(_("Importing Image")) as proc:
# Object name
name = outname or filename.split('/')[-1].split('\\')[-1]
units = self.app.defaults['units']
self.app.app_obj.new_object(obj_type, name, obj_init)
# Register recent file
self.app.file_opened.emit("image", filename)
# GUI feedback
self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename))
class ImageUI:
toolName = _("Image as Object")
def __init__(self, layout, app):
self.app = app
self.decimals = self.app.decimals
self.layout = layout
# ## Title
title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label.setStyleSheet(""" title_label.setStyleSheet("""
QLabel QLabel
{ {
@@ -150,48 +292,8 @@ class ToolImage(AppTool):
self.on_image_type(val=False) self.on_image_type(val=False)
# ## Signals # #################################### FINSIHED GUI ###########################
self.import_button.clicked.connect(self.on_file_importimage) # #############################################################################
self.image_type.activated_custom.connect(self.on_image_type)
def run(self, toggle=True):
self.app.defaults.report_usage("ToolImage()")
if toggle:
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
else:
try:
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
# if tab is populated with the tool but it does not have the focus, focus on it
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
# focus on Tool Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
else:
self.app.ui.splitter.setSizes([0, 1])
except AttributeError:
pass
else:
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
AppTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Image Tool"))
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, **kwargs)
def set_tool_ui(self):
# ## Initialize form
self.dpi_entry.set_value(96)
self.image_type.set_value('black')
self.mask_bw_entry.set_value(250)
self.mask_r_entry.set_value(250)
self.mask_g_entry.set_value(250)
self.mask_b_entry.set_value(250)
def on_image_type(self, val): def on_image_type(self, val):
if val == 'color': if val == 'color':
@@ -215,83 +317,19 @@ class ToolImage(AppTool):
self.mask_bw_label.setDisabled(False) self.mask_bw_label.setDisabled(False)
self.mask_bw_entry.setDisabled(False) self.mask_bw_entry.setDisabled(False)
def on_file_importimage(self): def confirmation_message(self, accepted, minval, maxval):
""" if accepted is False:
Callback for menu item File->Import IMAGE. self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
:param type_of_obj: to import the IMAGE as Geometry or as Gerber self.decimals,
:type type_of_obj: str minval,
:return: None self.decimals,
""" maxval), False)
mask = []
self.app.log.debug("on_file_importimage()")
_filter = "Image Files(*.BMP *.PNG *.JPG *.JPEG);;" \
"Bitmap File (*.BMP);;" \
"PNG File (*.PNG);;" \
"Jpeg File (*.JPG);;" \
"All Files (*.*)"
try:
filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"),
directory=self.app.get_last_folder(), filter=_filter)
except TypeError:
filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import IMAGE"), filter=filter)
filename = str(filename)
type_obj = self.tf_type_obj_combo.get_value()
dpi = self.dpi_entry.get_value()
mode = self.image_type.get_value()
mask = [self.mask_bw_entry.get_value(), self.mask_r_entry.get_value(), self.mask_g_entry.get_value(),
self.mask_b_entry.get_value()]
if filename == "":
self.app.inform.emit(_("Cancelled."))
else: else:
self.app.worker_task.emit({'fcn': self.import_image, self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
'params': [filename, type_obj, dpi, mode, mask]})
def import_image(self, filename, o_type=_("Gerber"), dpi=96, mode='black', mask=None, outname=None): def confirmation_message_int(self, accepted, minval, maxval):
""" if accepted is False:
Adds a new Geometry Object to the projects and populates self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
it with shapes extracted from the SVG file. (_("Edited value is out of range"), minval, maxval), False)
:param filename: Path to the SVG file.
:param o_type: type of FlatCAM objeect
:param dpi: dot per inch
:param mode: black or color
:param mask: dictate the level of detail
:param outname: name for the resulting file
:return:
"""
self.app.defaults.report_usage("import_image()")
if mask is None:
mask = [250, 250, 250, 250]
if o_type is None or o_type == _("Geometry"):
obj_type = "geometry"
elif o_type == _("Gerber"):
obj_type = "gerber"
else: else:
self.app.inform.emit('[ERROR_NOTCL] %s' % self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
_("Not supported type is picked as parameter. "
"Only Geometry and Gerber are supported"))
return
def obj_init(geo_obj, app_obj):
geo_obj.import_image(filename, units=units, dpi=dpi, mode=mode, mask=mask)
geo_obj.multigeo = False
with self.app.proc_container.new(_("Importing Image")) as proc:
# Object name
name = outname or filename.split('/')[-1].split('\\')[-1]
units = self.app.defaults['units']
self.app.app_obj.new_object(obj_type, name, obj_init)
# Register recent file
self.app.file_opened.emit("image", filename)
# GUI feedback
self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename))

View File

@@ -8,7 +8,7 @@
from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5 import QtWidgets, QtCore, QtGui
from appTool import AppTool from appTool import AppTool
from appGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox from appGUI.GUIElements import FCButton, FCDoubleSpinner, RadioSet, FCComboBox, FCLabel
from shapely.geometry import box from shapely.geometry import box
@@ -28,133 +28,20 @@ log = logging.getLogger('base')
class ToolInvertGerber(AppTool): class ToolInvertGerber(AppTool):
toolName = _("Invert Gerber Tool")
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
self.decimals = self.app.decimals self.decimals = self.app.decimals
AppTool.__init__(self, app) AppTool.__init__(self, app)
self.tools_frame = QtWidgets.QFrame() # #############################################################################
self.tools_frame.setContentsMargins(0, 0, 0, 0) # ######################### Tool GUI ##########################################
self.layout.addWidget(self.tools_frame) # #############################################################################
self.tools_box = QtWidgets.QVBoxLayout() self.ui = InvertUI(layout=self.layout, app=self.app)
self.tools_box.setContentsMargins(0, 0, 0, 0) self.toolName = self.ui.toolName
self.tools_frame.setLayout(self.tools_box)
# Title self.ui.invert_btn.clicked.connect(self.on_grb_invert)
title_label = QtWidgets.QLabel("%s" % self.toolName) self.ui.reset_button.clicked.connect(self.set_tool_ui)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.tools_box.addWidget(title_label)
# Grid Layout
grid0 = QtWidgets.QGridLayout()
grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1)
self.tools_box.addLayout(grid0)
grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
# Target Gerber Object
self.gerber_combo = FCComboBox()
self.gerber_combo.setModel(self.app.collection)
self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.gerber_combo.is_last = True
self.gerber_combo.obj_type = "Gerber"
self.gerber_label = QtWidgets.QLabel('<b>%s:</b>' % _("GERBER"))
self.gerber_label.setToolTip(
_("Gerber object that will be inverted.")
)
grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 3, 0, 1, 2)
self.param_label = QtWidgets.QLabel("<b>%s:</b>" % _("Parameters"))
self.param_label.setToolTip('%s.' % _("Parameters for this tool"))
grid0.addWidget(self.param_label, 4, 0, 1, 2)
# Margin
self.margin_label = QtWidgets.QLabel('%s:' % _('Margin'))
self.margin_label.setToolTip(
_("Distance by which to avoid\n"
"the edges of the Gerber object.")
)
self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.margin_entry.set_precision(self.decimals)
self.margin_entry.set_range(0.0000, 9999.9999)
self.margin_entry.setObjectName(_("Margin"))
grid0.addWidget(self.margin_label, 5, 0, 1, 2)
grid0.addWidget(self.margin_entry, 6, 0, 1, 2)
self.join_label = QtWidgets.QLabel('%s:' % _("Lines Join Style"))
self.join_label.setToolTip(
_("The way that the lines in the object outline will be joined.\n"
"Can be:\n"
"- rounded -> an arc is added between two joining lines\n"
"- square -> the lines meet in 90 degrees angle\n"
"- bevel -> the lines are joined by a third line")
)
self.join_radio = RadioSet([
{'label': 'Rounded', 'value': 'r'},
{'label': 'Square', 'value': 's'},
{'label': 'Bevel', 'value': 'b'}
], orientation='vertical', stretch=False)
grid0.addWidget(self.join_label, 7, 0, 1, 2)
grid0.addWidget(self.join_radio, 8, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 9, 0, 1, 2)
self.invert_btn = FCButton(_('Invert Gerber'))
self.invert_btn.setToolTip(
_("Will invert the Gerber object: areas that have copper\n"
"will be empty of copper and previous empty area will be\n"
"filled with copper.")
)
self.invert_btn.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.invert_btn, 10, 0, 1, 2)
self.tools_box.addStretch()
# ## Reset Tool
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
self.reset_button.setToolTip(
_("Will reset the tool parameters.")
)
self.reset_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.tools_box.addWidget(self.reset_button)
self.invert_btn.clicked.connect(self.on_grb_invert)
self.reset_button.clicked.connect(self.set_tool_ui)
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, shortcut='', **kwargs) AppTool.install(self, icon, separator, shortcut='', **kwargs)
@@ -188,20 +75,20 @@ class ToolInvertGerber(AppTool):
self.app.ui.notebook.setTabText(2, _("Invert Tool")) self.app.ui.notebook.setTabText(2, _("Invert Tool"))
def set_tool_ui(self): def set_tool_ui(self):
self.margin_entry.set_value(float(self.app.defaults["tools_invert_margin"])) self.ui.margin_entry.set_value(float(self.app.defaults["tools_invert_margin"]))
self.join_radio.set_value(self.app.defaults["tools_invert_join_style"]) self.ui.join_radio.set_value(self.app.defaults["tools_invert_join_style"])
def on_grb_invert(self): def on_grb_invert(self):
margin = self.margin_entry.get_value() margin = self.ui.margin_entry.get_value()
if round(margin, self.decimals) == 0.0: if round(margin, self.decimals) == 0.0:
margin = 1E-10 margin = 1E-10
join_style = {'r': 1, 'b': 3, 's': 2}[self.join_radio.get_value()] join_style = {'r': 1, 'b': 3, 's': 2}[self.ui.join_radio.get_value()]
if join_style is None: if join_style is None:
join_style = 'r' join_style = 'r'
grb_circle_steps = int(self.app.defaults["gerber_circle_steps"]) grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
obj_name = self.gerber_combo.currentText() obj_name = self.ui.gerber_combo.currentText()
outname = obj_name + "_inverted" outname = obj_name + "_inverted"
@@ -243,32 +130,6 @@ class ToolInvertGerber(AppTool):
new_apertures = {} new_apertures = {}
# for apid, val in grb_obj.apertures.items():
# new_apertures[apid] = {}
# for key in val:
# if key == 'geometry':
# new_apertures[apid]['geometry'] = []
# for elem in val['geometry']:
# geo_elem = {}
# if 'follow' in elem:
# try:
# geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior
# except AttributeError:
# # TODO should test if width or height is bigger
# geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior
# if 'clear' in elem:
# if isinstance(elem['clear'], Polygon):
# try:
# geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps)
# except AttributeError:
# # TODO should test if width or height is bigger
# geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps)
# else:
# geo_elem['follow'] = elem['clear']
# new_apertures[apid]['geometry'].append(deepcopy(geo_elem))
# else:
# new_apertures[apid][key] = deepcopy(val[key])
if '0' not in new_apertures: if '0' not in new_apertures:
new_apertures['0'] = {} new_apertures['0'] = {}
new_apertures['0']['type'] = 'C' new_apertures['0']['type'] = 'C'
@@ -302,9 +163,155 @@ class ToolInvertGerber(AppTool):
self.app.app_obj.new_object('gerber', outname, init_func) self.app.app_obj.new_object('gerber', outname, init_func)
def reset_fields(self): def reset_fields(self):
self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.ui.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@staticmethod @staticmethod
def poly2rings(poly): def poly2rings(poly):
return [poly.exterior] + [interior for interior in poly.interiors] return [poly.exterior] + [interior for interior in poly.interiors]
# end of file
class InvertUI:
toolName = _("Invert Gerber Tool")
def __init__(self, layout, app):
self.app = app
self.decimals = self.app.decimals
self.layout = layout
# ## Title
title_label = FCLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
self.layout.addWidget(FCLabel(""))
self.tools_frame = QtWidgets.QFrame()
self.tools_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.tools_frame)
self.tools_box = QtWidgets.QVBoxLayout()
self.tools_box.setContentsMargins(0, 0, 0, 0)
self.tools_frame.setLayout(self.tools_box)
# Grid Layout
grid0 = QtWidgets.QGridLayout()
grid0.setColumnStretch(0, 0)
grid0.setColumnStretch(1, 1)
self.tools_box.addLayout(grid0)
# Target Gerber Object
self.gerber_combo = FCComboBox()
self.gerber_combo.setModel(self.app.collection)
self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.gerber_combo.is_last = True
self.gerber_combo.obj_type = "Gerber"
self.gerber_label = FCLabel('<b>%s:</b>' % _("GERBER"))
self.gerber_label.setToolTip(
_("Gerber object that will be inverted.")
)
grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 3, 0, 1, 2)
self.param_label = FCLabel("<b>%s:</b>" % _("Parameters"))
self.param_label.setToolTip('%s.' % _("Parameters for this tool"))
grid0.addWidget(self.param_label, 4, 0, 1, 2)
# Margin
self.margin_label = FCLabel('%s:' % _('Margin'))
self.margin_label.setToolTip(
_("Distance by which to avoid\n"
"the edges of the Gerber object.")
)
self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
self.margin_entry.set_precision(self.decimals)
self.margin_entry.set_range(0.0000, 9999.9999)
self.margin_entry.setObjectName(_("Margin"))
grid0.addWidget(self.margin_label, 5, 0, 1, 2)
grid0.addWidget(self.margin_entry, 6, 0, 1, 2)
self.join_label = FCLabel('%s:' % _("Lines Join Style"))
self.join_label.setToolTip(
_("The way that the lines in the object outline will be joined.\n"
"Can be:\n"
"- rounded -> an arc is added between two joining lines\n"
"- square -> the lines meet in 90 degrees angle\n"
"- bevel -> the lines are joined by a third line")
)
self.join_radio = RadioSet([
{'label': 'Rounded', 'value': 'r'},
{'label': 'Square', 'value': 's'},
{'label': 'Bevel', 'value': 'b'}
], orientation='vertical', stretch=False)
grid0.addWidget(self.join_label, 7, 0, 1, 2)
grid0.addWidget(self.join_radio, 8, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 9, 0, 1, 2)
self.invert_btn = FCButton(_('Invert Gerber'))
self.invert_btn.setToolTip(
_("Will invert the Gerber object: areas that have copper\n"
"will be empty of copper and previous empty area will be\n"
"filled with copper.")
)
self.invert_btn.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
grid0.addWidget(self.invert_btn, 10, 0, 1, 2)
self.tools_box.addStretch()
# ## Reset Tool
self.reset_button = FCButton(_("Reset Tool"))
self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
self.reset_button.setToolTip(
_("Will reset the tool parameters.")
)
self.reset_button.setStyleSheet("""
QPushButton
{
font-weight: bold;
}
""")
self.tools_box.addWidget(self.reset_button)
# #################################### FINSIHED GUI ###########################
# #############################################################################
def confirmation_message(self, accepted, minval, maxval):
if accepted is False:
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
self.decimals,
minval,
self.decimals,
maxval), False)
else:
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
def confirmation_message_int(self, accepted, minval, maxval):
if accepted is False:
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
(_("Edited value is out of range"), minval, maxval), False)
else:
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)

View File

@@ -30,8 +30,6 @@ log = logging.getLogger('base')
class ToolOptimal(AppTool): class ToolOptimal(AppTool):
toolName = _("Optimal Tool")
update_text = QtCore.pyqtSignal(list) update_text = QtCore.pyqtSignal(list)
update_sec_distances = QtCore.pyqtSignal(dict) update_sec_distances = QtCore.pyqtSignal(dict)
@@ -41,12 +39,363 @@ class ToolOptimal(AppTool):
self.units = self.app.defaults['units'].upper() self.units = self.app.defaults['units'].upper()
self.decimals = self.app.decimals self.decimals = self.app.decimals
# #############################################################################
# ######################### Tool GUI ##########################################
# #############################################################################
self.ui = OptimalUI(layout=self.layout, app=self.app)
self.toolName = self.ui.toolName
# this is the line selected in the textbox with the locations of the minimum
self.selected_text = ''
# this is the line selected in the textbox with the locations of the other distances found in the Gerber object
self.selected_locations_text = ''
# dict to hold the distances between every two elements in Gerber as keys and the actual locations where that
# distances happen as values
self.min_dict = {}
# ############################################################################ # ############################################################################
# ############################ GUI creation ################################## # ############################ Signals #######################################
# ############################################################################
self.ui.calculate_button.clicked.connect(self.find_minimum_distance)
self.ui.locate_button.clicked.connect(self.on_locate_position)
self.ui.update_text.connect(self.on_update_text)
self.ui.locations_textb.cursorPositionChanged.connect(self.on_textbox_clicked)
self.ui.locate_sec_button.clicked.connect(self.on_locate_sec_position)
self.ui.update_sec_distances.connect(self.on_update_sec_distances_txt)
self.ui.distances_textb.cursorPositionChanged.connect(self.on_distances_textb_clicked)
self.ui.locations_sec_textb.cursorPositionChanged.connect(self.on_locations_sec_clicked)
self.ui.reset_button.clicked.connect(self.set_tool_ui)
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
def run(self, toggle=True):
self.app.defaults.report_usage("ToolOptimal()")
if toggle:
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
else:
try:
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
# if tab is populated with the tool but it does not have the focus, focus on it
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
# focus on Tool Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
else:
self.app.ui.splitter.setSizes([0, 1])
except AttributeError:
pass
else:
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
AppTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
def set_tool_ui(self):
self.ui.result_entry.set_value(0.0)
self.ui.freq_entry.set_value('0')
self.ui.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
self.ui.locations_textb.clear()
# new cursor - select all document
cursor = self.ui.locations_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
self.ui.locations_textb.setVisible(False)
self.ui.locate_button.setVisible(False)
self.ui.result_entry.set_value(0.0)
self.ui.freq_entry.set_value('0')
self.reset_fields()
def find_minimum_distance(self):
self.units = self.app.defaults['units'].upper()
self.decimals = int(self.ui.precision_spinner.get_value())
selection_index = self.ui.gerber_object_combo.currentIndex()
model_index = self.app.collection.index(selection_index, 0, self.ui.gerber_object_combo.rootModelIndex())
try:
fcobj = model_index.internalPointer().obj
except Exception as e:
log.debug("ToolOptimal.find_minimum_distance() --> %s" % str(e))
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
return
if fcobj.kind != 'gerber':
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
return
proc = self.app.proc_container.new(_("Working..."))
def job_thread(app_obj):
app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
try:
old_disp_number = 0
pol_nr = 0
app_obj.proc_container.update_view_text(' %d%%' % 0)
total_geo = []
for ap in list(fcobj.apertures.keys()):
if 'geometry' in fcobj.apertures[ap]:
app_obj.inform.emit(
'%s: %s' % (_("Optimal Tool. Parsing geometry for aperture"), str(ap)))
for geo_el in fcobj.apertures[ap]['geometry']:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
total_geo.append(geo_el['solid'])
app_obj.inform.emit(
_("Optimal Tool. Creating a buffer for the object geometry."))
total_geo = MultiPolygon(total_geo)
total_geo = total_geo.buffer(0)
try:
__ = iter(total_geo)
geo_len = len(total_geo)
geo_len = (geo_len * (geo_len - 1)) / 2
except TypeError:
app_obj.inform.emit('[ERROR_NOTCL] %s' %
_("The Gerber object has one Polygon as geometry.\n"
"There are no distances between geometry elements to be found."))
return 'fail'
app_obj.inform.emit(
'%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
str(geo_len)))
self.min_dict = {}
idx = 1
for geo in total_geo:
for s_geo in total_geo[idx:]:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
# minimize the number of distances by not taking into considerations those that are too small
dist = geo.distance(s_geo)
dist = float('%.*f' % (self.decimals, dist))
loc_1, loc_2 = nearest_points(geo, s_geo)
proc_loc = (
(float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
(float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
)
if dist in self.min_dict:
self.min_dict[dist].append(proc_loc)
else:
self.min_dict[dist] = [proc_loc]
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
app_obj.proc_container.update_view_text(' %d%%' % disp_number)
old_disp_number = disp_number
idx += 1
app_obj.inform.emit(_("Optimal Tool. Finding the minimum distance."))
min_list = list(self.min_dict.keys())
min_dist = min(min_list)
min_dist_string = '%.*f' % (self.decimals, float(min_dist))
self.ui.result_entry.set_value(min_dist_string)
freq = len(self.min_dict[min_dist])
freq = '%d' % int(freq)
self.ui.freq_entry.set_value(freq)
min_locations = self.min_dict.pop(min_dist)
self.update_text.emit(min_locations)
self.update_sec_distances.emit(self.min_dict)
app_obj.inform.emit('[success] %s' % _("Optimal Tool. Finished successfully."))
except Exception as ee:
proc.done()
log.debug(str(ee))
return
proc.done()
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def on_locate_position(self):
# cursor = self.locations_textb.textCursor()
# self.selected_text = cursor.selectedText()
try:
if self.selected_text != '':
loc = eval(self.selected_text)
else:
return 'fail'
except Exception as e:
log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e))
self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
"((x0, y0), (x1, y1)).")
return
try:
loc_1 = loc[0]
loc_2 = loc[1]
dx = loc_1[0] - loc_2[0]
dy = loc_1[1] - loc_2[1]
loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
self.app.on_jump_to(custom_location=loc)
except Exception as e:
log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e))
return
def on_update_text(self, data):
txt = ''
for loc in data:
if loc:
txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
self.ui.locations_textb.setPlainText(txt)
self.ui.locate_button.setDisabled(False)
def on_textbox_clicked(self):
# new cursor - select all document
cursor = self.ui.locations_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
# new cursor - select the current line
cursor = self.ui.locations_textb.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
# highlight the current selected line
tmp = cursor.blockFormat()
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
cursor.setBlockFormat(tmp)
self.selected_text = cursor.selectedText()
def on_update_sec_distances_txt(self, data):
distance_list = sorted(list(data.keys()))
txt = ''
for loc in distance_list:
txt += '%s\n' % str(loc)
self.ui.distances_textb.setPlainText(txt)
self.ui.locate_sec_button.setDisabled(False)
def on_distances_textb_clicked(self):
# new cursor - select all document
cursor = self.ui.distances_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
# new cursor - select the current line
cursor = self.ui.distances_textb.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
# highlight the current selected line
tmp = cursor.blockFormat()
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
cursor.setBlockFormat(tmp)
distance_text = cursor.selectedText()
key_in_min_dict = eval(distance_text)
self.on_update_locations_text(dist=key_in_min_dict)
def on_update_locations_text(self, dist):
distance_list = self.min_dict[dist]
txt = ''
for loc in distance_list:
if loc:
txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
self.ui.locations_sec_textb.setPlainText(txt)
def on_locations_sec_clicked(self):
# new cursor - select all document
cursor = self.ui.locations_sec_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
# new cursor - select the current line
cursor = self.ui.locations_sec_textb.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
# highlight the current selected line
tmp = cursor.blockFormat()
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
cursor.setBlockFormat(tmp)
self.selected_locations_text = cursor.selectedText()
def on_locate_sec_position(self):
try:
if self.selected_locations_text != '':
loc = eval(self.selected_locations_text)
else:
return
except Exception as e:
log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e))
self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
"((x0, y0), (x1, y1)).")
return
try:
loc_1 = loc[0]
loc_2 = loc[1]
dx = loc_1[0] - loc_2[0]
dy = loc_1[1] - loc_2[1]
loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
self.app.on_jump_to(custom_location=loc)
except Exception as e:
log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e))
return
def reset_fields(self):
self.ui.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.ui.gerber_object_combo.setCurrentIndex(0)
class OptimalUI:
toolName = _("Optimal Tool")
def __init__(self, layout, app):
self.app = app
self.decimals = self.app.decimals
self.layout = layout
self.units = self.app.defaults['units'].upper()
# ## Title # ## Title
title_label = QtWidgets.QLabel("%s" % self.toolName) title_label = QtWidgets.QLabel("%s" % self.toolName)
title_label.setStyleSheet( title_label.setStyleSheet("""
"""
QLabel QLabel
{ {
font-size: 16px; font-size: 16px;
@@ -54,13 +403,12 @@ class ToolOptimal(AppTool):
} }
""") """)
self.layout.addWidget(title_label) self.layout.addWidget(title_label)
self.layout.addWidget(QtWidgets.QLabel(""))
# ## Form Layout # ## Form Layout
form_lay = QtWidgets.QFormLayout() form_lay = QtWidgets.QFormLayout()
self.layout.addLayout(form_lay) self.layout.addLayout(form_lay)
form_lay.addRow(QtWidgets.QLabel(""))
# ## Gerber Object to mirror # ## Gerber Object to mirror
self.gerber_object_combo = FCComboBox() self.gerber_object_combo = FCComboBox()
self.gerber_object_combo.setModel(self.app.collection) self.gerber_object_combo.setModel(self.app.collection)
@@ -255,343 +603,23 @@ class ToolOptimal(AppTool):
self.loc_ois = OptionalHideInputSection(self.locations_cb, [self.locations_textb, self.locate_button]) self.loc_ois = OptionalHideInputSection(self.locations_cb, [self.locations_textb, self.locate_button])
self.sec_loc_ois = OptionalHideInputSection(self.sec_locations_cb, [self.sec_locations_frame]) self.sec_loc_ois = OptionalHideInputSection(self.sec_locations_cb, [self.sec_locations_frame])
# ################## Finished GUI creation ###################################
# ############################################################################
# this is the line selected in the textbox with the locations of the minimum # #################################### FINSIHED GUI ###########################
self.selected_text = '' # #############################################################################
# this is the line selected in the textbox with the locations of the other distances found in the Gerber object def confirmation_message(self, accepted, minval, maxval):
self.selected_locations_text = '' if accepted is False:
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
# dict to hold the distances between every two elements in Gerber as keys and the actual locations where that self.decimals,
# distances happen as values minval,
self.min_dict = {} self.decimals,
maxval), False)
# ############################################################################
# ############################ Signals #######################################
# ############################################################################
self.calculate_button.clicked.connect(self.find_minimum_distance)
self.locate_button.clicked.connect(self.on_locate_position)
self.update_text.connect(self.on_update_text)
self.locations_textb.cursorPositionChanged.connect(self.on_textbox_clicked)
self.locate_sec_button.clicked.connect(self.on_locate_sec_position)
self.update_sec_distances.connect(self.on_update_sec_distances_txt)
self.distances_textb.cursorPositionChanged.connect(self.on_distances_textb_clicked)
self.locations_sec_textb.cursorPositionChanged.connect(self.on_locations_sec_clicked)
self.reset_button.clicked.connect(self.set_tool_ui)
def install(self, icon=None, separator=None, **kwargs):
AppTool.install(self, icon, separator, shortcut='Alt+O', **kwargs)
def run(self, toggle=True):
self.app.defaults.report_usage("ToolOptimal()")
if toggle:
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
else: else:
try: self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
# if tab is populated with the tool but it does not have the focus, focus on it def confirmation_message_int(self, accepted, minval, maxval):
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab: if accepted is False:
# focus on Tool Tab self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) (_("Edited value is out of range"), minval, maxval), False)
else: else:
self.app.ui.splitter.setSizes([0, 1]) self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
except AttributeError:
pass
else:
if self.app.ui.splitter.sizes()[0] == 0:
self.app.ui.splitter.setSizes([1, 1])
AppTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
def set_tool_ui(self):
self.result_entry.set_value(0.0)
self.freq_entry.set_value('0')
self.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
self.locations_textb.clear()
# new cursor - select all document
cursor = self.locations_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
self.locations_textb.setVisible(False)
self.locate_button.setVisible(False)
self.result_entry.set_value(0.0)
self.freq_entry.set_value('0')
self.reset_fields()
def find_minimum_distance(self):
self.units = self.app.defaults['units'].upper()
self.decimals = int(self.precision_spinner.get_value())
selection_index = self.gerber_object_combo.currentIndex()
model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
try:
fcobj = model_index.internalPointer().obj
except Exception as e:
log.debug("ToolOptimal.find_minimum_distance() --> %s" % str(e))
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
return
if fcobj.kind != 'gerber':
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
return
proc = self.app.proc_container.new(_("Working..."))
def job_thread(app_obj):
app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
try:
old_disp_number = 0
pol_nr = 0
app_obj.proc_container.update_view_text(' %d%%' % 0)
total_geo = []
for ap in list(fcobj.apertures.keys()):
if 'geometry' in fcobj.apertures[ap]:
app_obj.inform.emit(
'%s: %s' % (_("Optimal Tool. Parsing geometry for aperture"), str(ap)))
for geo_el in fcobj.apertures[ap]['geometry']:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
total_geo.append(geo_el['solid'])
app_obj.inform.emit(
_("Optimal Tool. Creating a buffer for the object geometry."))
total_geo = MultiPolygon(total_geo)
total_geo = total_geo.buffer(0)
try:
__ = iter(total_geo)
geo_len = len(total_geo)
geo_len = (geo_len * (geo_len - 1)) / 2
except TypeError:
app_obj.inform.emit('[ERROR_NOTCL] %s' %
_("The Gerber object has one Polygon as geometry.\n"
"There are no distances between geometry elements to be found."))
return 'fail'
app_obj.inform.emit(
'%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
str(geo_len)))
self.min_dict = {}
idx = 1
for geo in total_geo:
for s_geo in total_geo[idx:]:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
# minimize the number of distances by not taking into considerations those that are too small
dist = geo.distance(s_geo)
dist = float('%.*f' % (self.decimals, dist))
loc_1, loc_2 = nearest_points(geo, s_geo)
proc_loc = (
(float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
(float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
)
if dist in self.min_dict:
self.min_dict[dist].append(proc_loc)
else:
self.min_dict[dist] = [proc_loc]
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
app_obj.proc_container.update_view_text(' %d%%' % disp_number)
old_disp_number = disp_number
idx += 1
app_obj.inform.emit(
_("Optimal Tool. Finding the minimum distance."))
min_list = list(self.min_dict.keys())
min_dist = min(min_list)
min_dist_string = '%.*f' % (self.decimals, float(min_dist))
self.result_entry.set_value(min_dist_string)
freq = len(self.min_dict[min_dist])
freq = '%d' % int(freq)
self.freq_entry.set_value(freq)
min_locations = self.min_dict.pop(min_dist)
self.update_text.emit(min_locations)
self.update_sec_distances.emit(self.min_dict)
app_obj.inform.emit('[success] %s' % _("Optimal Tool. Finished successfully."))
except Exception as ee:
proc.done()
log.debug(str(ee))
return
proc.done()
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def on_locate_position(self):
# cursor = self.locations_textb.textCursor()
# self.selected_text = cursor.selectedText()
try:
if self.selected_text != '':
loc = eval(self.selected_text)
else:
return 'fail'
except Exception as e:
log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e))
self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
"((x0, y0), (x1, y1)).")
return
try:
loc_1 = loc[0]
loc_2 = loc[1]
dx = loc_1[0] - loc_2[0]
dy = loc_1[1] - loc_2[1]
loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
self.app.on_jump_to(custom_location=loc)
except Exception as e:
log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e))
return
def on_update_text(self, data):
txt = ''
for loc in data:
if loc:
txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
self.locations_textb.setPlainText(txt)
self.locate_button.setDisabled(False)
def on_textbox_clicked(self):
# new cursor - select all document
cursor = self.locations_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
# new cursor - select the current line
cursor = self.locations_textb.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
# highlight the current selected line
tmp = cursor.blockFormat()
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
cursor.setBlockFormat(tmp)
self.selected_text = cursor.selectedText()
def on_update_sec_distances_txt(self, data):
distance_list = sorted(list(data.keys()))
txt = ''
for loc in distance_list:
txt += '%s\n' % str(loc)
self.distances_textb.setPlainText(txt)
self.locate_sec_button.setDisabled(False)
def on_distances_textb_clicked(self):
# new cursor - select all document
cursor = self.distances_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
# new cursor - select the current line
cursor = self.distances_textb.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
# highlight the current selected line
tmp = cursor.blockFormat()
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
cursor.setBlockFormat(tmp)
distance_text = cursor.selectedText()
key_in_min_dict = eval(distance_text)
self.on_update_locations_text(dist=key_in_min_dict)
def on_update_locations_text(self, dist):
distance_list = self.min_dict[dist]
txt = ''
for loc in distance_list:
if loc:
txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
self.locations_sec_textb.setPlainText(txt)
def on_locations_sec_clicked(self):
# new cursor - select all document
cursor = self.locations_sec_textb.textCursor()
cursor.select(QtGui.QTextCursor.Document)
# clear previous selection highlight
tmp = cursor.blockFormat()
tmp.clearBackground()
cursor.setBlockFormat(tmp)
# new cursor - select the current line
cursor = self.locations_sec_textb.textCursor()
cursor.select(QtGui.QTextCursor.LineUnderCursor)
# highlight the current selected line
tmp = cursor.blockFormat()
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
cursor.setBlockFormat(tmp)
self.selected_locations_text = cursor.selectedText()
def on_locate_sec_position(self):
try:
if self.selected_locations_text != '':
loc = eval(self.selected_locations_text)
else:
return
except Exception as e:
log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e))
self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format "
"((x0, y0), (x1, y1)).")
return
try:
loc_1 = loc[0]
loc_2 = loc[1]
dx = loc_1[0] - loc_2[0]
dy = loc_1[1] - loc_2[1]
loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
self.app.on_jump_to(custom_location=loc)
except Exception as e:
log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e))
return
def reset_fields(self):
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.gerber_object_combo.setCurrentIndex(0)

View File

@@ -8,7 +8,7 @@
from PyQt5 import QtWidgets, QtCore from PyQt5 import QtWidgets, QtCore
from appTool import AppTool from appTool import AppTool
from appGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable from appGUI.GUIElements import RadioSet, FCSpinner, FCButton, FCTable, FCLabel
import re import re
import os import os
@@ -28,121 +28,17 @@ class PcbWizard(AppTool):
file_loaded = QtCore.pyqtSignal(str, str) file_loaded = QtCore.pyqtSignal(str, str)
toolName = _("PcbWizard Import Tool")
def __init__(self, app): def __init__(self, app):
AppTool.__init__(self, app) AppTool.__init__(self, app)
self.app = app self.app = app
self.decimals = self.app.decimals self.decimals = self.app.decimals
# Title # #############################################################################
title_label = QtWidgets.QLabel("%s" % _('Import 2-file Excellon')) # ######################### Tool GUI ##########################################
title_label.setStyleSheet(""" # #############################################################################
QLabel self.ui = WizardUI(layout=self.layout, app=self.app)
{ self.toolName = self.ui.toolName
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
self.layout.addWidget(QtWidgets.QLabel(""))
self.layout.addWidget(QtWidgets.QLabel("<b>%s:</b>" % _("Load files")))
# Form Layout
form_layout = QtWidgets.QFormLayout()
self.layout.addLayout(form_layout)
self.excellon_label = QtWidgets.QLabel('%s:' % _("Excellon file"))
self.excellon_label.setToolTip(
_("Load the Excellon file.\n"
"Usually it has a .DRL extension")
)
self.excellon_brn = FCButton(_("Open"))
form_layout.addRow(self.excellon_label, self.excellon_brn)
self.inf_label = QtWidgets.QLabel('%s:' % _("INF file"))
self.inf_label.setToolTip(
_("Load the INF file.")
)
self.inf_btn = FCButton(_("Open"))
form_layout.addRow(self.inf_label, self.inf_btn)
self.tools_table = FCTable()
self.layout.addWidget(self.tools_table)
self.tools_table.setColumnCount(2)
self.tools_table.setHorizontalHeaderLabels(['#Tool', _('Diameter')])
self.tools_table.horizontalHeaderItem(0).setToolTip(
_("Tool Number"))
self.tools_table.horizontalHeaderItem(1).setToolTip(
_("Tool diameter in file units."))
# start with apertures table hidden
self.tools_table.setVisible(False)
self.layout.addWidget(QtWidgets.QLabel(""))
self.layout.addWidget(QtWidgets.QLabel("<b>%s:</b>" % _("Excellon format")))
# Form Layout
form_layout1 = QtWidgets.QFormLayout()
self.layout.addLayout(form_layout1)
# Integral part of the coordinates
self.int_entry = FCSpinner(callback=self.confirmation_message_int)
self.int_entry.set_range(1, 10)
self.int_label = QtWidgets.QLabel('%s:' % _("Int. digits"))
self.int_label.setToolTip(
_("The number of digits for the integral part of the coordinates.")
)
form_layout1.addRow(self.int_label, self.int_entry)
# Fractional part of the coordinates
self.frac_entry = FCSpinner(callback=self.confirmation_message_int)
self.frac_entry.set_range(1, 10)
self.frac_label = QtWidgets.QLabel('%s:' % _("Frac. digits"))
self.frac_label.setToolTip(
_("The number of digits for the fractional part of the coordinates.")
)
form_layout1.addRow(self.frac_label, self.frac_entry)
# Zeros suppression for coordinates
self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
{'label': _('TZ'), 'value': 'TZ'},
{'label': _('No Suppression'), 'value': 'D'}])
self.zeros_label = QtWidgets.QLabel('%s:' % _("Zeros supp."))
self.zeros_label.setToolTip(
_("The type of zeros suppression used.\n"
"Can be of type:\n"
"- LZ = leading zeros are kept\n"
"- TZ = trailing zeros are kept\n"
"- No Suppression = no zero suppression")
)
form_layout1.addRow(self.zeros_label, self.zeros_radio)
# Units type
self.units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
{'label': _('MM'), 'value': 'METRIC'}])
self.units_label = QtWidgets.QLabel("<b>%s:</b>" % _('Units'))
self.units_label.setToolTip(
_("The type of units that the coordinates and tool\n"
"diameters are using. Can be INCH or MM.")
)
form_layout1.addRow(self.units_label, self.units_radio)
# Buttons
self.import_button = QtWidgets.QPushButton(_("Import Excellon"))
self.import_button.setToolTip(
_("Import in FlatCAM an Excellon file\n"
"that store it's information's in 2 files.\n"
"One usually has .DRL extension while\n"
"the other has .INF extension.")
)
self.layout.addWidget(self.import_button)
self.layout.addStretch()
self.excellon_loaded = False self.excellon_loaded = False
self.inf_loaded = False self.inf_loaded = False
@@ -151,13 +47,13 @@ class PcbWizard(AppTool):
self.modified_excellon_file = '' self.modified_excellon_file = ''
# ## Signals # ## Signals
self.excellon_brn.clicked.connect(self.on_load_excellon_click) self.ui.excellon_brn.clicked.connect(self.on_load_excellon_click)
self.inf_btn.clicked.connect(self.on_load_inf_click) self.ui.inf_btn.clicked.connect(self.on_load_inf_click)
self.import_button.clicked.connect(lambda: self.on_import_excellon( self.ui.import_button.clicked.connect(lambda: self.on_import_excellon(
excellon_fileobj=self.modified_excellon_file)) excellon_fileobj=self.modified_excellon_file))
self.file_loaded.connect(self.on_file_loaded) self.file_loaded.connect(self.on_file_loaded)
self.units_radio.activated_custom.connect(self.on_units_change) self.ui.units_radio.activated_custom.connect(self.ui.on_units_change)
self.units = 'INCH' self.units = 'INCH'
self.zeros = 'LZ' self.zeros = 'LZ'
@@ -211,10 +107,10 @@ class PcbWizard(AppTool):
self.tools_from_inf = {} self.tools_from_inf = {}
# ## Initialize form # ## Initialize form
self.int_entry.set_value(self.integral) self.ui.int_entry.set_value(self.integral)
self.frac_entry.set_value(self.fractional) self.ui.frac_entry.set_value(self.fractional)
self.zeros_radio.set_value(self.zeros) self.ui.zeros_radio.set_value(self.zeros)
self.units_radio.set_value(self.units) self.ui.units_radio.set_value(self.units)
self.excellon_loaded = False self.excellon_loaded = False
self.inf_loaded = False self.inf_loaded = False
@@ -227,57 +123,49 @@ class PcbWizard(AppTool):
sorted_tools = [] sorted_tools = []
if not self.tools_from_inf: if not self.tools_from_inf:
self.tools_table.setVisible(False) self.ui.tools_table.setVisible(False)
else: else:
sort = [] sort = []
for k, v in list(self.tools_from_inf.items()): for k, v in list(self.tools_from_inf.items()):
sort.append(int(k)) sort.append(int(k))
sorted_tools = sorted(sort) sorted_tools = sorted(sort)
n = len(sorted_tools) n = len(sorted_tools)
self.tools_table.setRowCount(n) self.ui.tools_table.setRowCount(n)
tool_row = 0 tool_row = 0
for tool in sorted_tools: for tool in sorted_tools:
tool_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool)) tool_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool))
tool_id_item.setFlags(QtCore.Qt.ItemIsEnabled) tool_id_item.setFlags(QtCore.Qt.ItemIsEnabled)
self.tools_table.setItem(tool_row, 0, tool_id_item) # Tool name/id self.ui.tools_table.setItem(tool_row, 0, tool_id_item) # Tool name/id
tool_dia_item = QtWidgets.QTableWidgetItem(str(self.tools_from_inf[tool])) tool_dia_item = QtWidgets.QTableWidgetItem(str(self.tools_from_inf[tool]))
tool_dia_item.setFlags(QtCore.Qt.ItemIsEnabled) tool_dia_item.setFlags(QtCore.Qt.ItemIsEnabled)
self.tools_table.setItem(tool_row, 1, tool_dia_item) self.ui.tools_table.setItem(tool_row, 1, tool_dia_item)
tool_row += 1 tool_row += 1
self.tools_table.resizeColumnsToContents() self.ui.tools_table.resizeColumnsToContents()
self.tools_table.resizeRowsToContents() self.ui.tools_table.resizeRowsToContents()
vertical_header = self.tools_table.verticalHeader() vertical_header = self.ui.tools_table.verticalHeader()
vertical_header.hide() vertical_header.hide()
self.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
horizontal_header = self.tools_table.horizontalHeader() horizontal_header = self.ui.tools_table.horizontalHeader()
# horizontal_header.setMinimumSectionSize(10) # horizontal_header.setMinimumSectionSize(10)
# horizontal_header.setDefaultSectionSize(70) # horizontal_header.setDefaultSectionSize(70)
horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
self.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.tools_table.setSortingEnabled(False) self.ui.tools_table.setSortingEnabled(False)
self.tools_table.setMinimumHeight(self.tools_table.getHeight()) self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight())
self.tools_table.setMaximumHeight(self.tools_table.getHeight()) self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight())
def update_params(self): def update_params(self):
self.units = self.units_radio.get_value() self.units = self.ui.units_radio.get_value()
self.zeros = self.zeros_radio.get_value() self.zeros = self.ui.zeros_radio.get_value()
self.integral = self.int_entry.get_value() self.integral = self.ui.int_entry.get_value()
self.fractional = self.frac_entry.get_value() self.fractional = self.ui.frac_entry.get_value()
def on_units_change(self, val):
if val == 'INCH':
self.int_entry.set_value(2)
self.frac_entry.set_value(4)
else:
self.int_entry.set_value(3)
self.frac_entry.set_value(3)
def on_load_excellon_click(self): def on_load_excellon_click(self):
""" """
@@ -357,9 +245,9 @@ class PcbWizard(AppTool):
self.units = 'INCH' self.units = 'INCH'
else: else:
self.units = 'METRIC' self.units = 'METRIC'
self.units_radio.set_value(self.units) self.ui.units_radio.set_value(self.units)
self.int_entry.set_value(self.integral) self.ui.int_entry.set_value(self.integral)
self.frac_entry.set_value(self.fractional) self.ui.frac_entry.set_value(self.fractional)
if not self.tools_from_inf: if not self.tools_from_inf:
self.app.inform.emit('[ERROR] %s' % self.app.inform.emit('[ERROR] %s' %
@@ -382,14 +270,12 @@ class PcbWizard(AppTool):
if signal == 'inf': if signal == 'inf':
self.inf_loaded = True self.inf_loaded = True
self.tools_table.setVisible(True) self.ui.tools_table.setVisible(True)
self.app.inform.emit('[success] %s' % self.app.inform.emit('[success] %s' % _("PcbWizard .INF file loaded."))
_("PcbWizard .INF file loaded."))
elif signal == 'excellon': elif signal == 'excellon':
self.excellon_loaded = True self.excellon_loaded = True
self.outname = os.path.split(str(filename))[1] self.outname = os.path.split(str(filename))[1]
self.app.inform.emit('[success] %s' % self.app.inform.emit('[success] %s' % _("Main PcbWizard Excellon file loaded."))
_("Main PcbWizard Excellon file loaded."))
if self.excellon_loaded and self.inf_loaded: if self.excellon_loaded and self.inf_loaded:
self.update_params() self.update_params()
@@ -467,3 +353,149 @@ class PcbWizard(AppTool):
self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...')) self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...'))
else: else:
self.app.inform.emit('[ERROR_NOTCL] %s' % _('The imported Excellon file is empty.')) self.app.inform.emit('[ERROR_NOTCL] %s' % _('The imported Excellon file is empty.'))
class WizardUI:
toolName = _("PcbWizard Import Tool")
def __init__(self, layout, app):
self.app = app
self.decimals = self.app.decimals
self.layout = layout
# ## Title
title_label = FCLabel("%s" % self.toolName)
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
self.layout.addWidget(FCLabel(""))
self.layout.addWidget(FCLabel("<b>%s:</b>" % _("Load files")))
# Form Layout
form_layout = QtWidgets.QFormLayout()
self.layout.addLayout(form_layout)
self.excellon_label = FCLabel('%s:' % _("Excellon file"))
self.excellon_label.setToolTip(
_("Load the Excellon file.\n"
"Usually it has a .DRL extension")
)
self.excellon_brn = FCButton(_("Open"))
form_layout.addRow(self.excellon_label, self.excellon_brn)
self.inf_label = FCLabel('%s:' % _("INF file"))
self.inf_label.setToolTip(
_("Load the INF file.")
)
self.inf_btn = FCButton(_("Open"))
form_layout.addRow(self.inf_label, self.inf_btn)
self.tools_table = FCTable()
self.layout.addWidget(self.tools_table)
self.tools_table.setColumnCount(2)
self.tools_table.setHorizontalHeaderLabels(['#Tool', _('Diameter')])
self.tools_table.horizontalHeaderItem(0).setToolTip(
_("Tool Number"))
self.tools_table.horizontalHeaderItem(1).setToolTip(
_("Tool diameter in file units."))
# start with apertures table hidden
self.tools_table.setVisible(False)
self.layout.addWidget(FCLabel(""))
self.layout.addWidget(FCLabel("<b>%s:</b>" % _("Excellon format")))
# Form Layout
form_layout1 = QtWidgets.QFormLayout()
self.layout.addLayout(form_layout1)
# Integral part of the coordinates
self.int_entry = FCSpinner(callback=self.confirmation_message_int)
self.int_entry.set_range(1, 10)
self.int_label = FCLabel('%s:' % _("Int. digits"))
self.int_label.setToolTip(
_("The number of digits for the integral part of the coordinates.")
)
form_layout1.addRow(self.int_label, self.int_entry)
# Fractional part of the coordinates
self.frac_entry = FCSpinner(callback=self.confirmation_message_int)
self.frac_entry.set_range(1, 10)
self.frac_label = FCLabel('%s:' % _("Frac. digits"))
self.frac_label.setToolTip(
_("The number of digits for the fractional part of the coordinates.")
)
form_layout1.addRow(self.frac_label, self.frac_entry)
# Zeros suppression for coordinates
self.zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'LZ'},
{'label': _('TZ'), 'value': 'TZ'},
{'label': _('No Suppression'), 'value': 'D'}])
self.zeros_label = FCLabel('%s:' % _("Zeros supp."))
self.zeros_label.setToolTip(
_("The type of zeros suppression used.\n"
"Can be of type:\n"
"- LZ = leading zeros are kept\n"
"- TZ = trailing zeros are kept\n"
"- No Suppression = no zero suppression")
)
form_layout1.addRow(self.zeros_label, self.zeros_radio)
# Units type
self.units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
{'label': _('MM'), 'value': 'METRIC'}])
self.units_label = FCLabel("<b>%s:</b>" % _('Units'))
self.units_label.setToolTip(
_("The type of units that the coordinates and tool\n"
"diameters are using. Can be INCH or MM.")
)
form_layout1.addRow(self.units_label, self.units_radio)
# Buttons
self.import_button = QtWidgets.QPushButton(_("Import Excellon"))
self.import_button.setToolTip(
_("Import in FlatCAM an Excellon file\n"
"that store it's information's in 2 files.\n"
"One usually has .DRL extension while\n"
"the other has .INF extension.")
)
self.layout.addWidget(self.import_button)
self.layout.addStretch()
# #################################### FINSIHED GUI ###########################
# #############################################################################
def on_units_change(self, val):
if val == 'INCH':
self.int_entry.set_value(2)
self.frac_entry.set_value(4)
else:
self.int_entry.set_value(3)
self.frac_entry.set_value(3)
def confirmation_message(self, accepted, minval, maxval):
if accepted is False:
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
self.decimals,
minval,
self.decimals,
maxval), False)
else:
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
def confirmation_message_int(self, accepted, minval, maxval):
if accepted is False:
self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
(_("Edited value is out of range"), minval, maxval), False)
else:
self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)