diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index 57248bef..4c422a9d 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -94,7 +94,7 @@ class App(QtCore.QObject):
# Version
version = 8.910
- version_date = "2019/02/22"
+ version_date = "2019/02/23"
beta = True
# current date now
@@ -158,9 +158,9 @@ class App(QtCore.QObject):
# Calls 'on_zoom_fit' method to fit object in scene view in main thread to prevent drawing glitches.
object_plotted = QtCore.pyqtSignal(object)
- # Emitted when a new object has been added to the collection
- # and is ready to be used.
- new_object_available = QtCore.pyqtSignal(object)
+ # Emitted when a new object has been added or deleted from/to the collection
+ object_status_changed = QtCore.pyqtSignal(object, str)
+
message = QtCore.pyqtSignal(str, str, str)
# Emmited when shell command is finished(one command only)
@@ -2456,7 +2456,6 @@ class App(QtCore.QObject):
elif obj.kind == 'geometry':
self.inform.emit('[selected]%s created/selected: %s' %
(obj.kind.capitalize(), 'red', str(obj.options['name'])))
- # self.new_object_available.emit(obj)
# update the SHELL auto-completer model with the name of the new object
self.myKeywords.append(obj.options['name'])
diff --git a/GUIElements.py b/GUIElements.py
index f14e4dcf..75ca4aac 100644
--- a/GUIElements.py
+++ b/GUIElements.py
@@ -475,9 +475,25 @@ class FCTextAreaRich(QtWidgets.QTextEdit):
class FCComboBox(QtWidgets.QComboBox):
- def __init__(self, parent=None):
+
+ def __init__(self, parent=None, callback=None):
super(FCComboBox, self).__init__(parent)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
+ self.view = self.view()
+ self.view.viewport().installEventFilter(self)
+ self.view.setContextMenuPolicy(Qt.CustomContextMenu)
+
+ # the callback() will be called on customcontextmenu event and will be be passed 2 parameters:
+ # pos = mouse right click click position
+ # self = is the combobox object itself
+ if callback:
+ self.view.customContextMenuRequested.connect(lambda pos: callback(pos, self))
+
+ def eventFilter(self, obj, event):
+ if event.type() == QtCore.QEvent.MouseButtonRelease:
+ if event.button() == Qt.RightButton:
+ return True
+ return False
def wheelEvent(self, *args, **kwargs):
pass
diff --git a/ObjectCollection.py b/ObjectCollection.py
index 291f3666..be98ed07 100644
--- a/ObjectCollection.py
+++ b/ObjectCollection.py
@@ -686,6 +686,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.app.should_we_save = True
+ self.app.object_status_changed.emit(obj, 'append')
+
# decide if to show or hide the Notebook side of the screen
if self.app.defaults["global_project_autohide"] is True:
# always open the notebook on object added to collection
@@ -761,6 +763,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
active = selections[0].internalPointer()
group = active.parent_item
+ # send signal with the object that is deleted
+ # self.app.object_status_changed.emit(active.obj, 'delete')
+
# update the SHELL auto-completer model data
name = active.obj.options['name']
try:
diff --git a/README.md b/README.md
index 2569f8be..269e141f 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@ CAD program, and create G-Code for Isolation routing.
- removed "added ability to regenerate objects (it's actually deletion followed by recreation)" because of the way Python pass parameters to functions by reference instead of copy
- added ability to toggle globally the display of ToolTips. Edit -> Preferences -> General -> Enable ToolTips checkbox.
- added true fullscreen support (for Windows OS)
+- added the ability of context menu inside the GuiElements.FCCombobox() object.
+- remade the UI for ToolSolderPaste. The object comboboxes now have context menu's that allow object deletion. Also the last object created is set as current item in comboboxes.
21.02.2019
diff --git a/flatcamTools/ToolSolderPaste.py b/flatcamTools/ToolSolderPaste.py
index fc8fd0ab..54cbb774 100644
--- a/flatcamTools/ToolSolderPaste.py
+++ b/flatcamTools/ToolSolderPaste.py
@@ -1,13 +1,13 @@
from FlatCAMTool import FlatCAMTool
-from copy import copy,deepcopy
from ObjectCollection import *
from FlatCAMApp import *
-from PyQt5 import QtGui, QtCore, QtWidgets
from GUIElements import IntEntry, RadioSet, LengthEntry
from FlatCAMCommon import LoudDict
-
from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
+from PyQt5 import QtGui, QtCore, QtWidgets
+from copy import copy,deepcopy
+
class SolderPaste(FlatCAMTool):
@@ -32,7 +32,7 @@ class SolderPaste(FlatCAMTool):
self.layout.addLayout(obj_form_layout)
## Gerber Object to be used for solderpaste dispensing
- self.obj_combo = QtWidgets.QComboBox()
+ self.obj_combo = FCComboBox(callback=self.on_rmb_combo)
self.obj_combo.setModel(self.app.collection)
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.obj_combo.setCurrentIndex(1)
@@ -71,9 +71,6 @@ class SolderPaste(FlatCAMTool):
"Nozzle tool Diameter. It's value (in current FlatCAM units)\n"
"is the width of the solder paste dispensed.")
- self.empty_label = QtWidgets.QLabel('')
- self.layout.addWidget(self.empty_label)
-
#### Add a new Tool ####
hlay_tools = QtWidgets.QHBoxLayout()
self.layout.addLayout(hlay_tools)
@@ -113,24 +110,22 @@ class SolderPaste(FlatCAMTool):
# grid2.addWidget(self.copytool_btn, 0, 1)
grid0.addWidget(self.deltool_btn, 0, 2)
- ## Form Layout
- geo_form_layout = QtWidgets.QFormLayout()
- self.layout.addLayout(geo_form_layout)
+ self.layout.addSpacing(10)
- ## Geometry Object to be used for solderpaste dispensing
- self.geo_obj_combo = QtWidgets.QComboBox()
- self.geo_obj_combo.setModel(self.app.collection)
- self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
- self.geo_obj_combo.setCurrentIndex(1)
+ ## Buttons
+ grid0_1 = QtWidgets.QGridLayout()
+ self.layout.addLayout(grid0_1)
- self.geo_object_label = QtWidgets.QLabel("Geometry:")
- self.geo_object_label.setToolTip(
- "Geometry Solder paste object.\n"
- "In order to enable the GCode generation section,\n"
- "the name of the object has to end in:\n"
- "'_solderpaste' as a protection."
+ step1_lbl = QtWidgets.QLabel("STEP 1:")
+ step1_lbl.setToolTip(
+ "First step is to select a number of nozzle tools for usage\n"
+ "and then optionally modify the GCode parameters bellow."
)
- geo_form_layout.addRow(self.geo_object_label, self.geo_obj_combo)
+ step1_description_lbl = QtWidgets.QLabel("Select tools.\n"
+ "Modify parameters.")
+
+ grid0_1.addWidget(step1_lbl, 0, 0, alignment=Qt.AlignTop)
+ grid0_1.addWidget(step1_description_lbl, 0, 2, alignment=Qt.AlignBottom)
self.gcode_frame = QtWidgets.QFrame()
self.gcode_frame.setContentsMargins(0, 0, 0, 0)
@@ -275,17 +270,70 @@ class SolderPaste(FlatCAMTool):
"on PCB pads."
)
+ self.generation_frame = QtWidgets.QFrame()
+ self.generation_frame.setContentsMargins(0, 0, 0, 0)
+ self.layout.addWidget(self.generation_frame)
+ self.generation_box = QtWidgets.QVBoxLayout()
+ self.generation_box.setContentsMargins(0, 0, 0, 0)
+ self.generation_frame.setLayout(self.generation_box)
+
+
+ ## Buttons
+ grid2 = QtWidgets.QGridLayout()
+ self.generation_box.addLayout(grid2)
+
+ step2_lbl = QtWidgets.QLabel("STEP 2:")
+ step2_lbl.setToolTip(
+ "Second step is to create a solder paste dispensing\n"
+ "geometry out of an Solder Paste Mask Gerber file."
+ )
+ grid2.addWidget(step2_lbl, 0, 0)
+ grid2.addWidget(self.soldergeo_btn, 0, 2)
+
+ ## Form Layout
+ geo_form_layout = QtWidgets.QFormLayout()
+ self.generation_box.addLayout(geo_form_layout)
+
+ ## Geometry Object to be used for solderpaste dispensing
+ self.geo_obj_combo = FCComboBox(callback=self.on_rmb_combo)
+ self.geo_obj_combo.setModel(self.app.collection)
+ self.geo_obj_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
+ self.geo_obj_combo.setCurrentIndex(1)
+
+ self.geo_object_label = QtWidgets.QLabel("Geo Result:")
+ self.geo_object_label.setToolTip(
+ "Geometry Solder Paste object.\n"
+ "The name of the object has to end in:\n"
+ "'_solderpaste' as a protection."
+ )
+ geo_form_layout.addRow(self.geo_object_label, self.geo_obj_combo)
+
+ grid3 = QtWidgets.QGridLayout()
+ self.generation_box.addLayout(grid3)
+
+ step3_lbl = QtWidgets.QLabel("STEP 3:")
+ step3_lbl.setToolTip(
+ "Third step is to select a solder paste dispensing geometry,\n"
+ "and then generate a CNCJob object.\n\n"
+ "REMEMBER: if you want to create a CNCJob with new parameters,\n"
+ "first you need to generate a geometry with those new params,\n"
+ "and only after that you can generate an updated CNCJob."
+ )
+
+ grid3.addWidget(step3_lbl, 0, 0)
+ grid3.addWidget(self.solder_gcode_btn, 0, 2)
+
## Form Layout
cnc_form_layout = QtWidgets.QFormLayout()
- self.gcode_box.addLayout(cnc_form_layout)
+ self.generation_box.addLayout(cnc_form_layout)
## Gerber Object to be used for solderpaste dispensing
- self.cnc_obj_combo = QtWidgets.QComboBox()
+ self.cnc_obj_combo = FCComboBox(callback=self.on_rmb_combo)
self.cnc_obj_combo.setModel(self.app.collection)
self.cnc_obj_combo.setRootModelIndex(self.app.collection.index(3, 0, QtCore.QModelIndex()))
self.cnc_obj_combo.setCurrentIndex(1)
- self.cnc_object_label = QtWidgets.QLabel("CNCJob: ")
+ self.cnc_object_label = QtWidgets.QLabel("CNC Result:")
self.cnc_object_label.setToolTip(
"CNCJob Solder paste object.\n"
"In order to enable the GCode save section,\n"
@@ -294,36 +342,8 @@ class SolderPaste(FlatCAMTool):
)
cnc_form_layout.addRow(self.cnc_object_label, self.cnc_obj_combo)
- self.save_gcode_frame = QtWidgets.QFrame()
- self.save_gcode_frame.setContentsMargins(0, 0, 0, 0)
- self.layout.addWidget(self.save_gcode_frame)
- self.save_gcode_box = QtWidgets.QVBoxLayout()
- self.save_gcode_box.setContentsMargins(0, 0, 0, 0)
- self.save_gcode_frame.setLayout(self.save_gcode_box)
-
-
- ## Buttons
- grid2 = QtWidgets.QGridLayout()
- self.save_gcode_box.addLayout(grid2)
-
- step1_lbl = QtWidgets.QLabel("STEP 1:")
- step1_lbl.setToolTip(
- "First step is to select a number of nozzle tools for usage\n"
- "and then create a solder paste dispensing geometry out of an\n"
- "Solder Paste Mask Gerber file."
- )
- grid2.addWidget(step1_lbl, 0, 0)
- grid2.addWidget(self.soldergeo_btn, 0, 2)
-
- step2_lbl = QtWidgets.QLabel("STEP 2:")
- step2_lbl.setToolTip(
- "Second step is to select a solder paste dispensing geometry,\n"
- "set the CAM parameters and then generate a CNCJob object which\n"
- "will pe painted on canvas in blue color."
- )
-
- grid2.addWidget(step2_lbl, 1, 0)
- grid2.addWidget(self.solder_gcode_btn, 1, 2)
+ grid4 = QtWidgets.QGridLayout()
+ self.generation_box.addLayout(grid4)
self.solder_gcode_view_btn = QtWidgets.QPushButton("View GCode")
self.solder_gcode_view_btn.setToolTip(
@@ -337,15 +357,15 @@ class SolderPaste(FlatCAMTool):
"on PCB pads, to a file."
)
- step3_lbl = QtWidgets.QLabel("STEP 3:")
- step3_lbl.setToolTip(
- "Third step (and last) is to select a CNCJob made from \n"
+ step4_lbl = QtWidgets.QLabel("STEP 4:")
+ step4_lbl.setToolTip(
+ "Fourth step (and last) is to select a CNCJob made from \n"
"a solder paste dispensing geometry, and then view/save it's GCode."
)
- grid2.addWidget(step3_lbl, 2, 0)
- grid2.addWidget(self.solder_gcode_view_btn, 2, 2)
- grid2.addWidget(self.solder_gcode_save_btn, 3, 2)
+ grid4.addWidget(step4_lbl, 0, 0)
+ grid4.addWidget(self.solder_gcode_view_btn, 0, 2)
+ grid4.addWidget(self.solder_gcode_save_btn, 1, 2)
self.layout.addStretch()
@@ -360,7 +380,14 @@ class SolderPaste(FlatCAMTool):
self.units = ''
+ # this will be used in the combobox context menu, for delete entry
+ self.obj_to_be_deleted_name = ''
+
+ # action to be added in the combobox context menu
+ self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon('share/trash16.png'), "Delete Object")
+
## Signals
+ self.combo_context_del_action.triggered.connect(self.on_delete_object)
self.addtool_btn.clicked.connect(self.on_tool_add)
self.deltool_btn.clicked.connect(self.on_tool_delete)
self.soldergeo_btn.clicked.connect(self.on_create_geo_click)
@@ -369,9 +396,10 @@ class SolderPaste(FlatCAMTool):
self.solder_gcode_save_btn.clicked.connect(self.on_save_gcode)
self.geo_obj_combo.currentIndexChanged.connect(self.on_geo_select)
-
self.cnc_obj_combo.currentIndexChanged.connect(self.on_cncjob_select)
+ self.app.object_status_changed.connect(self.update_comboboxes)
+
def run(self):
self.app.report_usage("ToolSolderPaste()")
@@ -449,6 +477,10 @@ class SolderPaste(FlatCAMTool):
self.reset_fields()
def build_ui(self):
+ """
+ Will rebuild the UI populating it (tools table)
+ :return:
+ """
self.ui_disconnect()
# updated units
@@ -519,6 +551,11 @@ class SolderPaste(FlatCAMTool):
self.ui_connect()
def update_ui(self, row=None):
+ """
+ Will update the UI form with the data from obj.tools
+ :param row: the row (tool) from which to extract information's used to populate the form
+ :return:
+ """
self.ui_disconnect()
if row is None:
@@ -587,6 +624,31 @@ class SolderPaste(FlatCAMTool):
except:
pass
+ def update_comboboxes(self, obj, status):
+ """
+ Modify the current text of the comboboxes to show the last object
+ that was created.
+
+ :param obj: object that was changed and called this PyQt slot
+ :param status: what kind of change happened: 'append' or 'delete'
+ :return:
+ """
+ obj_name = obj.options['name']
+
+ if status == 'append':
+ idx = self.obj_combo.findText(obj_name)
+ if idx != -1:
+ self.obj_combo.setCurrentIndex(idx)
+
+ idx = self.geo_obj_combo.findText(obj_name)
+ if idx != -1:
+ self.geo_obj_combo.setCurrentIndex(idx)
+
+ idx = self.cnc_obj_combo.findText(obj_name)
+ if idx != -1:
+ self.cnc_obj_combo.setCurrentIndex(idx)
+ print(obj_name)
+
def read_form_to_options(self):
"""
Will read all the parameters from Solder Paste Tool UI and update the self.options dictionary
@@ -597,7 +659,11 @@ class SolderPaste(FlatCAMTool):
self.options[key] = self.form_fields[key].get_value()
def read_form_to_tooldata(self, tooluid=None):
-
+ """
+ Will read all the items in the UI form and set the self.tools data accordingly
+ :param tooluid: the uid of the tool to be updated in the obj.tools
+ :return:
+ """
current_row = self.tools_table.currentRow()
uid = tooluid if tooluid else int(self.tools_table.item(current_row, 2).text())
for key in self.form_fields:
@@ -631,7 +697,13 @@ class SolderPaste(FlatCAMTool):
self.form_fields[key].set_value(val[key])
def on_tool_add(self, dia=None, muted=None):
+ """
+ Add a Tool in the Tool Table
+ :param dia: diameter of the tool to be added
+ :param muted: if True will not send status bar messages about adding tools
+ :return:
+ """
self.ui_disconnect()
if dia:
@@ -694,6 +766,10 @@ class SolderPaste(FlatCAMTool):
self.build_ui()
def on_tool_edit(self):
+ """
+ Edit a tool in the Tool Table
+ :return:
+ """
self.ui_disconnect()
tool_dias = []
@@ -735,6 +811,13 @@ class SolderPaste(FlatCAMTool):
self.build_ui()
def on_tool_delete(self, rows_to_delete=None, all=None):
+ """
+ Will delete tool(s) in the Tool Table
+
+ :param rows_to_delete: tell which row (tool) to delete
+ :param all: to delete all tools at once
+ :return:
+ """
self.ui_disconnect()
deleted_tools_list = []
@@ -777,6 +860,35 @@ class SolderPaste(FlatCAMTool):
self.app.inform.emit("[success] Nozzle tool(s) deleted from Tool Table.")
self.build_ui()
+ def on_rmb_combo(self, pos, combo):
+ """
+ Will create a context menu on the combobox items
+ :param pos: mouse click position passed by the signal that called this slot
+ :param combo: the actual combo from where the signal was triggered
+ :return:
+ """
+ view = combo.view
+ idx = view.indexAt(pos)
+ if not idx.isValid():
+ return
+
+ self.obj_to_be_deleted_name = combo.model().itemData(idx)[0]
+
+ menu = QtWidgets.QMenu()
+ menu.addAction(self.combo_context_del_action)
+ menu.exec(view.mapToGlobal(pos))
+
+ def on_delete_object(self):
+ """
+ Slot for the 'delete' action triggered in the combobox context menu.
+ The name of the object to be deleted is collected when the combobox context menu is created.
+ :return:
+ """
+ if self.obj_to_be_deleted_name != '':
+ self.app.collection.set_active(self.obj_to_be_deleted_name)
+ self.app.collection.delete_active(select_project=False)
+ self.obj_to_be_deleted_name = ''
+
def on_geo_select(self):
# if self.geo_obj_combo.currentText().rpartition('_')[2] == 'solderpaste':
# self.gcode_frame.setDisabled(False)
@@ -796,6 +908,12 @@ class SolderPaste(FlatCAMTool):
return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
def on_create_geo_click(self, signal):
+ """
+ Will create a solderpaste dispensing geometry.
+
+ :param signal: passed by the signal that called this slot
+ :return:
+ """
name = self.obj_combo.currentText()
if name == '':
self.app.inform.emit("[WARNING_NOTCL] No SolderPaste mask Gerber object loaded.")
@@ -808,6 +926,13 @@ class SolderPaste(FlatCAMTool):
self.on_create_geo(name=name, work_object=obj)
def on_create_geo(self, name, work_object):
+ """
+ The actual work for creating solderpaste dispensing geometry is done here.
+
+ :param name: the outname for the resulting geometry object
+ :param work_object: the source Gerber object from which the geometry is created
+ :return: a Geometry type object
+ """
proc = self.app.proc_container.new("Creating Solder Paste dispensing geometry.")
obj = work_object
@@ -932,8 +1057,8 @@ class SolderPaste(FlatCAMTool):
try:
app_obj.new_object("geometry", name + "_solderpaste", geo_init)
except Exception as e:
+ log.error("SolderPaste.on_create_geo() --> %s" % str(e))
proc.done()
- traceback.print_stack()
return
proc.done()
@@ -942,10 +1067,16 @@ class SolderPaste(FlatCAMTool):
self.app.collection.promise(name)
# Background
- self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app.paste_tool]})
+ self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
# self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
def on_create_gcode_click(self, signal):
+ """
+ Will create a CNCJob object from the solderpaste dispensing geometry.
+
+ :param signal: parameter passed by the signal that called this slot
+ :return:
+ """
name = self.geo_obj_combo.currentText()
obj = self.app.collection.get_by_name(name)
@@ -962,17 +1093,22 @@ class SolderPaste(FlatCAMTool):
return 'fail'
# use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia
- originar_name = obj.options['name'].rpartition('_')[0]
+ originar_name = obj.options['name'].partition('_')[0]
outname = "%s_%s" % (originar_name, 'cnc_solderpaste')
self.on_create_gcode(name=outname, workobject=obj)
def on_create_gcode(self, name, workobject, use_thread=True):
"""
- Creates a multi-tool CNCJob out of this Geometry object.
- :return: None
+ Creates a multi-tool CNCJob. The actual work is done here.
+
+ :param name: outname for the resulting CNCJob object
+ :param workobject: the solderpaste dispensing Geometry object that is the source
+ :param use_thread: True if threaded execution is desired
+ :return:
"""
+
obj = workobject
try:
@@ -1063,6 +1199,11 @@ class SolderPaste(FlatCAMTool):
self.app.new_object("cncjob", name, job_init)
def on_view_gcode(self):
+ """
+ View GCode in the Editor Tab.
+
+ :return:
+ """
time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
# add the tab if it was closed
@@ -1124,6 +1265,11 @@ class SolderPaste(FlatCAMTool):
self.app.ui.show()
def on_save_gcode(self):
+ """
+ Save sodlerpaste dispensing GCode to a file on HDD.
+
+ :return:
+ """
time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
name = self.cnc_obj_combo.currentText()
obj = self.app.collection.get_by_name(name)