From 299a6585a73bab175071f3ebaa0c622a0a92c052 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 00:16:14 +0200 Subject: [PATCH 01/27] - done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it - fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation - fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. --- FlatCAMApp.py | 56 +++++++++++++++++++-------------------- FlatCAMGUI.py | 2 +- FlatCAMObj.py | 16 ++++++++--- ObjectCollection.py | 4 +-- README.md | 3 +++ camlib.py | 2 +- flatcamTools/ToolPaint.py | 31 ++++++++++++++++++++++ 7 files changed, 79 insertions(+), 35 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index d4f8f99b..5893168d 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -6367,34 +6367,34 @@ The normal flow when working in FlatCAM is the following:

self.ui.selected_scroll_area.setWidget(sel_title) - tool_title = QtWidgets.QTextEdit( - 'Shortcut Key List') - tool_title.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) - tool_title.setFrameStyle(QtWidgets.QFrame.NoFrame) - # font = self.sel_title.font() - # font.setPointSize(12) - # self.sel_title.setFont(font) - - tool_text = ''' -

Tool Tab - Choose an Item in Tools Menu

- -

Details:
-Some of the functionality of FlatCAM have been implemented as tools (a sort of plugins).

- -

Most of the tools are accessible through the Tools menu or by using the associated shortcut keys.
-Each such a tool, if it needs an object to be used as a source it will provide the way to select this object(s) through a series of comboboxes. The result of using a tool is either a Geometry, an information that can be used in the app or it can be a file that can be saved.

- -
    -
- -

A list of key shortcuts is available through an menu entry in Help -> Shortcuts List or through it's own key shortcut: '`' (key left to 1).

- - ''' - - tool_title.setText(tool_text) - tool_title.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - - self.ui.tool_scroll_area.setWidget(tool_title) +# tool_title = QtWidgets.QTextEdit( +# 'Shortcut Key List') +# tool_title.setTextInteractionFlags(QtCore.Qt.NoTextInteraction) +# tool_title.setFrameStyle(QtWidgets.QFrame.NoFrame) +# # font = self.sel_title.font() +# # font.setPointSize(12) +# # self.sel_title.setFont(font) +# +# tool_text = ''' +#

Tool Tab - Choose an Item in Tools Menu

+# +#

Details:
+# Some of the functionality of FlatCAM have been implemented as tools (a sort of plugins).

+# +#

Most of the tools are accessible through the Tools menu or by using the associated shortcut keys.
+# Each such a tool, if it needs an object to be used as a source it will provide the way to select this object(s) through a series of comboboxes. The result of using a tool is either a Geometry, an information that can be used in the app or it can be a file that can be saved.

+# +#
    +#
+# +#

A list of key shortcuts is available through an menu entry in Help -> Shortcuts List or through it's own key shortcut: '`' (key left to 1).

+# +# ''' +# +# tool_title.setText(tool_text) +# tool_title.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) +# +# self.ui.tool_scroll_area.setWidget(tool_title) def setup_obj_classes(self): """ diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 87436aad..4cd70820 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -3064,7 +3064,7 @@ class GeometryOptPrefGroupUI(OptionsGroupUI): ) grid1.addWidget(segy_label, 21, 0) self.segy_entry = FCEntry() - grid1.addWidget(self.segy_entry, 22, 1) + grid1.addWidget(self.segy_entry, 21, 1) self.layout.addStretch() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index cd720a90..d0eea1f8 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -3349,6 +3349,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): job_obj.multigeo = True job_obj.cnc_tools.clear() + job_obj.options['xmin'] = xmin + job_obj.options['ymin'] = ymin + job_obj.options['xmax'] = xmax + job_obj.options['ymax'] = ymax + try: job_obj.z_pdepth = float(self.options["z_pdepth"]) except ValueError: @@ -4301,9 +4306,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): self.app.ui.code_editor.clear() # then append the text from GCode to the text editor - for line in self.app.gcode_edited: - proc_line = str(line).strip('\n') - self.app.ui.code_editor.append(proc_line) + try: + for line in self.app.gcode_edited: + proc_line = str(line).strip('\n') + self.app.ui.code_editor.append(proc_line) + except Exception as e: + log.debug('FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e)) + self.app.inform.emit('[ERROR]FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e)) + return self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start) diff --git a/ObjectCollection.py b/ObjectCollection.py index ff967a74..f9530f6f 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -875,8 +875,8 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.set_inactive(name) def on_list_selection_change(self, current, previous): - FlatCAMApp.App.log.debug("on_list_selection_change()") - FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous))) + # FlatCAMApp.App.log.debug("on_list_selection_change()") + # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous))) try: obj = current.indexes()[0].internalPointer().obj diff --git a/README.md b/README.md index 7b09ea0a..9df214d2 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ CAD program, and create G-Code for Isolation routing. 6.02.2019 - fixed the units calculators crash FlatCAM when using comma as decimal separator +- done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it +- fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation +- fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. 5.02.3019 diff --git a/camlib.py b/camlib.py index 1178b716..3fe32d44 100644 --- a/camlib.py +++ b/camlib.py @@ -829,7 +829,7 @@ class Geometry(object): for i in current.interiors: geoms.insert(i) else: - print("Current Area is zero") + log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero") break # Optimization: Reduce lifts diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py index 0604d9ac..0beb996b 100644 --- a/flatcamTools/ToolPaint.py +++ b/flatcamTools/ToolPaint.py @@ -839,6 +839,17 @@ class ToolPaint(FlatCAMTool, Gerber): return None geo_obj.solid_geometry = [] + + try: + a, b, c, d = poly.bounds() + geo_obj.options['xmin'] = a + geo_obj.options['ymin'] = b + geo_obj.options['xmax'] = c + geo_obj.options['ymax'] = d + except Exception as e: + log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e)) + return + try: poly_buf = poly.buffer(-paint_margin) if isinstance(poly_buf, MultiPolygon): @@ -988,6 +999,16 @@ class ToolPaint(FlatCAMTool, Gerber): sorted_tools.append(float(self.tools_table.item(row, 1).text())) sorted_tools.sort(reverse=True) + try: + a, b, c, d = obj.bounds() + geo_obj.options['xmin'] = a + geo_obj.options['ymin'] = b + geo_obj.options['xmax'] = c + geo_obj.options['ymax'] = d + except Exception as e: + log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e)) + return + total_geometry = [] current_uid = int(1) geo_obj.solid_geometry = [] @@ -1085,6 +1106,16 @@ class ToolPaint(FlatCAMTool, Gerber): current_uid = int(1) geo_obj.solid_geometry = [] + try: + a, b, c, d = obj.bounds() + geo_obj.options['xmin'] = a + geo_obj.options['ymin'] = b + geo_obj.options['xmax'] = c + geo_obj.options['ymax'] = d + except Exception as e: + log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e)) + return + for tool_dia in sorted_tools: for geo in recurse(obj.solid_geometry): try: From b589292c0f008939191cf5b101f97233094c38fa Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 02:17:29 +0200 Subject: [PATCH 02/27] - fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it. --- FlatCAMObj.py | 14 +++++++------- README.md | 1 + camlib.py | 28 ++++++++++++++-------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index d0eea1f8..e8dbdf65 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -388,7 +388,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry) def __init__(self, name): - Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"]) + Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"])) FlatCAMObj.__init__(self, name) self.kind = "gerber" @@ -833,7 +833,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): optionChanged = QtCore.pyqtSignal(str) def __init__(self, name): - Excellon.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"]) + Excellon.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"])) FlatCAMObj.__init__(self, name) self.kind = "excellon" @@ -1590,20 +1590,20 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" for slot in self.slots: if slot['tool'] in tools: - buffer_value = self.tools[slot['tool']]["C"] / 2 - tooldia / 2 + buffer_value = (float(self.tools[slot['tool']]["C"]) / 2) - float(tooldia / 2) if buffer_value == 0: start = slot['start'] stop = slot['stop'] lines_string = LineString([start, stop]) - poly = lines_string.buffer(0.0000001, self.geo_steps_per_circle).exterior + poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior geo_obj.solid_geometry.append(poly) else: start = slot['start'] stop = slot['stop'] lines_string = LineString([start, stop]) - poly = lines_string.buffer(buffer_value, self.geo_steps_per_circle).exterior + poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior geo_obj.solid_geometry.append(poly) if use_thread: @@ -1967,7 +1967,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): def __init__(self, name): FlatCAMObj.__init__(self, name) - Geometry.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"]) + Geometry.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"])) self.kind = "geometry" @@ -4020,7 +4020,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): CNCjob.__init__(self, units=units, kind=kind, z_move=z_move, feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia, - spindlespeed=spindlespeed, steps_per_circle=self.app.defaults["cncjob_steps_per_circle"]) + spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"])) FlatCAMObj.__init__(self, name) diff --git a/README.md b/README.md index 9df214d2..e261f25d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ CAD program, and create G-Code for Isolation routing. - done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it - fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation - fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. +- fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it. 5.02.3019 diff --git a/camlib.py b/camlib.py index 3fe32d44..cd6f5658 100644 --- a/camlib.py +++ b/camlib.py @@ -101,7 +101,7 @@ class Geometry(object): self.geo_steps_per_circle = geo_steps_per_circle if geo_steps_per_circle is None: - geo_steps_per_circle = Geometry.defaults["geo_steps_per_circle"] + geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"]) self.geo_steps_per_circle = geo_steps_per_circle def make_index(self): @@ -537,7 +537,7 @@ class Geometry(object): if offset == 0: geo_iso = self.solid_geometry else: - geo_iso = self.solid_geometry.buffer(offset, int(self.geo_steps_per_circle / 4)) + geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)) # end of replaced block if iso_type == 2: @@ -790,7 +790,7 @@ class Geometry(object): # Can only result in a Polygon or MultiPolygon # NOTE: The resulting polygon can be "empty". - current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle / 4)) + current = polygon.buffer((-tooldia / 1.999999), int(int(steps_per_circle) / 4)) if current.area == 0: # Otherwise, trying to to insert current.exterior == None # into the FlatCAMStorage will fail. @@ -813,7 +813,7 @@ class Geometry(object): while True: # Can only result in a Polygon or MultiPolygon - current = current.buffer(-tooldia * (1 - overlap), int(steps_per_circle / 4)) + current = current.buffer(-tooldia * (1 - overlap), int(int(steps_per_circle) / 4)) if current.area > 0: # current can be a MultiPolygon @@ -835,7 +835,7 @@ class Geometry(object): # Optimization: Reduce lifts if connect: # log.debug("Reducing tool lifts...") - geoms = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle) + geoms = Geometry.paint_connect(geoms, polygon, tooldia, int(steps_per_circle)) return geoms @@ -1873,11 +1873,11 @@ class Gerber (Geometry): # How to discretize a circle. if steps_per_circle is None: - steps_per_circle = Gerber.defaults['steps_per_circle'] - self.steps_per_circle = steps_per_circle + steps_per_circle = int(Gerber.defaults['steps_per_circle']) + self.steps_per_circle = int(steps_per_circle) # Initialize parent - Geometry.__init__(self, geo_steps_per_circle=steps_per_circle) + Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle)) self.solid_geometry = Polygon() @@ -3268,10 +3268,10 @@ class Excellon(Geometry): """ if geo_steps_per_circle is None: - geo_steps_per_circle = Excellon.defaults['geo_steps_per_circle'] - self.geo_steps_per_circle = geo_steps_per_circle + geo_steps_per_circle = int(Excellon.defaults['geo_steps_per_circle']) + self.geo_steps_per_circle = int(geo_steps_per_circle) - Geometry.__init__(self, geo_steps_per_circle=geo_steps_per_circle) + Geometry.__init__(self, geo_steps_per_circle=int(geo_steps_per_circle)) # dictionary to store tools, see above for description self.tools = {} @@ -4382,10 +4382,10 @@ class CNCjob(Geometry): # Used when parsing G-code arcs if steps_per_circle is None: - steps_per_circle = CNCjob.defaults["steps_per_circle"] - self.steps_per_circle = steps_per_circle + steps_per_circle = int(CNCjob.defaults["steps_per_circle"]) + self.steps_per_circle = int(steps_per_circle) - Geometry.__init__(self, geo_steps_per_circle=steps_per_circle) + Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle)) self.kind = kind self.units = units From dedf8c09dea7b436f6adac6e0f47218a99a7ec18 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 14:03:59 +0200 Subject: [PATCH 03/27] - fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. - made the shell toggle shortcut key work when focused on Selected Tab; toggle units shortcut also - changed the messages for Units COnversion --- FlatCAMApp.py | 16 ++- FlatCAMGUI.py | 6 + FlatCAMObj.py | 58 ++++++--- README.md | 4 + camlib.py | 329 +++++++++++++++++++++++++++----------------------- 5 files changed, 236 insertions(+), 177 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 5893168d..151e50a5 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1659,7 +1659,10 @@ class App(QtCore.QObject): return self.defaults["global_last_folder"] def get_last_save_folder(self): - return self.defaults["global_last_save_folder"] + loc = self.defaults["global_last_save_folder"] + if loc is None: + loc = self.defaults["global_last_folder"] + return loc def report_usage(self, resource): """ @@ -2070,6 +2073,8 @@ class App(QtCore.QObject): except: self.inform.emit("[ERROR_NOTCL] Failed to write defaults to file.") return + + self.file_saved.emit("preferences", filename) self.inform.emit("[success]Exported Defaults to %s" % filename) def on_preferences_open_folder(self): @@ -2825,6 +2830,9 @@ class App(QtCore.QObject): current.to_form() self.plot_all() + self.inform.emit("[success]Converted units to %s" % self.options["units"]) + # self.ui.units_label.setText("[" + self.options["units"] + "]") + self.set_screen_units(self.options["units"]) else: # Undo toggling self.toggle_units_ignore = True @@ -2833,11 +2841,9 @@ class App(QtCore.QObject): else: self.general_options_form.general_app_group.units_radio.set_value('MM') self.toggle_units_ignore = False + self.inform.emit("[WARNING_NOTCL]Units conversion cancelled.") self.options_read_form() - self.inform.emit("Converted units to %s" % self.options["units"]) - #self.ui.units_label.setText("[" + self.options["units"] + "]") - self.set_screen_units(self.options["units"]) def on_toggle_units_click(self): if self.options["units"] == 'MM': @@ -6044,7 +6050,7 @@ class App(QtCore.QObject): self.defaults["global_last_folder"] = os.path.split(str(filename))[0] def register_save_folder(self, filename): - self.defaults['global_last_save_folder'] = os.path.split(str(filename))[0] + self.defaults["global_last_save_folder"] = os.path.split(str(filename))[0] def set_progress_bar(self, percentage, text=""): self.ui.progress_bar.setValue(int(percentage)) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 4cd70820..b6a39a53 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1508,6 +1508,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if event.key() == QtCore.Qt.Key_3: self.app.on_select_tab('tool') + if event.key == QtCore.Qt.Key_Q: + self.app.on_toggle_units_click() + + if event.key() == QtCore.Qt.Key_S: + self.app.on_toggle_shell() + # Show shortcut list if event.key() == QtCore.Qt.Key_Ampersand: self.app.on_shortcut_list() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index e8dbdf65..645c4573 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1564,7 +1564,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): return False, "Error: No tools." for tool in tools: - if tooldia > self.tools[tool]["C"]: + # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse + adj_toolstable_tooldia = float('%.4f' % float(tooldia)) + adj_file_tooldia = float('%.4f' % float(self.tools[tool]["C"])) + if adj_toolstable_tooldia > adj_file_tooldia + 0.0001: self.app.inform.emit("[ERROR_NOTCL] Milling tool for SLOTS is larger than hole size. Cancelled.") return False, "Error: Milling tool is larger than hole." @@ -1590,7 +1593,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" for slot in self.slots: if slot['tool'] in tools: - buffer_value = (float(self.tools[slot['tool']]["C"]) / 2) - float(tooldia / 2) + toolstable_tool = float('%.4f' % float(tooldia)) + file_tool = float('%.4f' % float(self.tools[tool]["C"])) + + # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse + # for the file_tool (tooldia actually) + buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001 if buffer_value == 0: start = slot['start'] stop = slot['stop'] @@ -1729,14 +1737,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # job_obj.options["tooldia"] = tools_csv = ','.join(tools) - job_obj.generate_from_excellon_by_tool(self, tools_csv, - drillz=self.options['drillz'], - toolchange=self.options["toolchange"], - toolchangez=self.options["toolchangez"], - startz=self.options["startz"], - endz=self.options["endz"], - excellon_optimization_type=self.options["optimization_type"]) - + ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, + drillz=self.options['drillz'], + toolchange=self.options["toolchange"], + toolchangez=self.options["toolchangez"], + startz=self.options["startz"], + endz=self.options["endz"], + excellon_optimization_type=self.app.defaults[ + "excellon_optimization_type"]) + if ret_val == 'fail': + return 'fail' app_obj.progress.emit(50) job_obj.gcode_parse() @@ -3125,10 +3135,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) - xmin = self.options['xmin'] - ymin = self.options['ymin'] - xmax = self.options['xmax'] - ymax = self.options['ymax'] + try: + xmin = self.options['xmin'] + ymin = self.options['ymin'] + xmax = self.options['xmax'] + ymax = self.options['ymax'] + except Exception as e: + log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e)) + msg = "[ERROR] An internal error has ocurred. See shell.\n" + msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e) + msg += traceback.format_exc() + self.app.inform.emit(msg) + return # Object initialization function for app.new_object() # RUNNING ON SEPARATE THREAD! @@ -4267,14 +4285,17 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \ "G-Code Files (*.g-code);;All Files (*.*)" + dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) try: - filename = str(QtWidgets.QFileDialog.getSaveFileName( + filename, _ = QtWidgets.QFileDialog.getSaveFileName( caption="Export Machine Code ...", - directory=self.app.get_last_save_folder() + '/' + name, + directory=dir_file_to_save, filter=_filter_ - )[0]) + ) except TypeError: - filename = str(QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)[0]) + filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_) + + filename = str(filename) if filename == '': self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...") @@ -4482,6 +4503,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): elif to_file is False: # Just for adding it to the recent files list. self.app.file_opened.emit("cncjob", filename) + self.app.file_saved.emit("cncjob", filename) self.app.inform.emit("[success] Saved to: " + filename) else: diff --git a/README.md b/README.md index e261f25d..84448839 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ CAD program, and create G-Code for Isolation routing. - fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation - fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective. - fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it. +- fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None +- fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. +- made the shell toggle shortcut key work when focused on Selected Tab; toggle units shortcut also +- changed the messages for Units COnversion 5.02.3019 diff --git a/camlib.py b/camlib.py index cd6f5658..b85eb553 100644 --- a/camlib.py +++ b/camlib.py @@ -4549,7 +4549,7 @@ class CNCjob(Geometry): elif drillz == 0: self.app.inform.emit("[WARNING] The Cut Z parameter is zero. " "There will be no cut, skipping %s file" % exobj.options['name']) - return + return 'fail' else: self.z_cut = drillz @@ -4670,139 +4670,188 @@ class CNCjob(Geometry): if current_platform == '64bit': if excellon_optimization_type == 'M': log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.") - for tool in tools: - self.tool=tool - self.postdata['toolC']=exobj.tools[tool]["C"] + if exobj.drills: + for tool in tools: + self.tool=tool + self.postdata['toolC']=exobj.tools[tool]["C"] - ################################################ - # Create the data. - node_list = [] - locations = create_data_array() - tsp_size = len(locations) - num_routes = 1 # The number of routes, which is 1 in the TSP. - # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. - depot = 0 - # Create routing model. - if tsp_size > 0: - routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) - search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() - search_parameters.local_search_metaheuristic = ( - routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) + ################################################ + # Create the data. + node_list = [] + locations = create_data_array() + tsp_size = len(locations) + num_routes = 1 # The number of routes, which is 1 in the TSP. + # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. + depot = 0 + # Create routing model. + if tsp_size > 0: + routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) + search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() + search_parameters.local_search_metaheuristic = ( + routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) - # Set search time limit in milliseconds. - if float(self.app.defaults["excellon_search_time"]) != 0: - search_parameters.time_limit_ms = int( - float(self.app.defaults["excellon_search_time"]) * 1000) + # Set search time limit in milliseconds. + if float(self.app.defaults["excellon_search_time"]) != 0: + search_parameters.time_limit_ms = int( + float(self.app.defaults["excellon_search_time"]) * 1000) + else: + search_parameters.time_limit_ms = 3000 + + # Callback to the distance function. The callback takes two + # arguments (the from and to node indices) and returns the distance between them. + dist_between_locations = CreateDistanceCallback() + dist_callback = dist_between_locations.Distance + routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) + + # Solve, returns a solution if any. + assignment = routing.SolveWithParameters(search_parameters) + + if assignment: + # Solution cost. + log.info("Total distance: " + str(assignment.ObjectiveValue())) + + # Inspect solution. + # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. + route_number = 0 + node = routing.Start(route_number) + start_node = node + + while not routing.IsEnd(node): + node_list.append(node) + node = assignment.Value(routing.NextVar(node)) + else: + log.warning('No solution found.') else: - search_parameters.time_limit_ms = 3000 + log.warning('Specify an instance greater than 0.') + ################################################ - # Callback to the distance function. The callback takes two - # arguments (the from and to node indices) and returns the distance between them. - dist_between_locations = CreateDistanceCallback() - dist_callback = dist_between_locations.Distance - routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) + # Only if tool has points. + if tool in points: + # Tool change sequence (optional) + if toolchange: + gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) + gcode += self.doformat(p.spindle_code) # Spindle start + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time + else: + gcode += self.doformat(p.spindle_code) + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time - # Solve, returns a solution if any. - assignment = routing.SolveWithParameters(search_parameters) + # Drillling! + for k in node_list: + locx = locations[k][0] + locy = locations[k][1] - if assignment: - # Solution cost. - log.info("Total distance: " + str(assignment.ObjectiveValue())) + gcode += self.doformat(p.rapid_code, x=locx, y=locy) + gcode += self.doformat(p.down_code, x=locx, y=locy) + gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) + gcode += self.doformat(p.lift_code, x=locx, y=locy) + measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) + self.oldx = locx + self.oldy = locy + else: + log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " + "The loaded Excellon file has no drills ...") + self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...') + return 'fail' - # Inspect solution. - # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. - route_number = 0 - node = routing.Start(route_number) - start_node = node - - while not routing.IsEnd(node): - node_list.append(node) - node = assignment.Value(routing.NextVar(node)) - else: - log.warning('No solution found.') - else: - log.warning('Specify an instance greater than 0.') - ################################################ - - # Only if tool has points. - if tool in points: - # Tool change sequence (optional) - if toolchange: - gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) - gcode += self.doformat(p.spindle_code) # Spindle start - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time - else: - gcode += self.doformat(p.spindle_code) - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time - - # Drillling! - for k in node_list: - locx = locations[k][0] - locy = locations[k][1] - - gcode += self.doformat(p.rapid_code, x=locx, y=locy) - gcode += self.doformat(p.down_code, x=locx, y=locy) - gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) - gcode += self.doformat(p.lift_code, x=locx, y=locy) - measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) - self.oldx = locx - self.oldy = locy log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance)) elif excellon_optimization_type == 'B': log.debug("Using OR-Tools Basic drill path optimization.") - for tool in tools: - self.tool=tool - self.postdata['toolC']=exobj.tools[tool]["C"] + if exobj.drills: + for tool in tools: + self.tool=tool + self.postdata['toolC']=exobj.tools[tool]["C"] - ################################################ - node_list = [] - locations = create_data_array() - tsp_size = len(locations) - num_routes = 1 # The number of routes, which is 1 in the TSP. + ################################################ + node_list = [] + locations = create_data_array() + tsp_size = len(locations) + num_routes = 1 # The number of routes, which is 1 in the TSP. - # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. - depot = 0 + # Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route. + depot = 0 - # Create routing model. - if tsp_size > 0: - routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) - search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() + # Create routing model. + if tsp_size > 0: + routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot) + search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() - # Callback to the distance function. The callback takes two - # arguments (the from and to node indices) and returns the distance between them. - dist_between_locations = CreateDistanceCallback() - dist_callback = dist_between_locations.Distance - routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) + # Callback to the distance function. The callback takes two + # arguments (the from and to node indices) and returns the distance between them. + dist_between_locations = CreateDistanceCallback() + dist_callback = dist_between_locations.Distance + routing.SetArcCostEvaluatorOfAllVehicles(dist_callback) - # Solve, returns a solution if any. - assignment = routing.SolveWithParameters(search_parameters) + # Solve, returns a solution if any. + assignment = routing.SolveWithParameters(search_parameters) - if assignment: - # Solution cost. - log.info("Total distance: " + str(assignment.ObjectiveValue())) + if assignment: + # Solution cost. + log.info("Total distance: " + str(assignment.ObjectiveValue())) - # Inspect solution. - # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. - route_number = 0 - node = routing.Start(route_number) - start_node = node + # Inspect solution. + # Only one route here; otherwise iterate from 0 to routing.vehicles() - 1. + route_number = 0 + node = routing.Start(route_number) + start_node = node - while not routing.IsEnd(node): - node_list.append(node) - node = assignment.Value(routing.NextVar(node)) + while not routing.IsEnd(node): + node_list.append(node) + node = assignment.Value(routing.NextVar(node)) + else: + log.warning('No solution found.') else: - log.warning('No solution found.') - else: - log.warning('Specify an instance greater than 0.') - ################################################ + log.warning('Specify an instance greater than 0.') + ################################################ + + # Only if tool has points. + if tool in points: + # Tool change sequence (optional) + if toolchange: + gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) + gcode += self.doformat(p.spindle_code) # Spindle start) + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time + else: + gcode += self.doformat(p.spindle_code) + if self.dwell is True: + gcode += self.doformat(p.dwell_code) # Dwell time + + # Drillling! + for k in node_list: + locx = locations[k][0] + locy = locations[k][1] + gcode += self.doformat(p.rapid_code, x=locx, y=locy) + gcode += self.doformat(p.down_code, x=locx, y=locy) + gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) + gcode += self.doformat(p.lift_code, x=locx, y=locy) + measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) + self.oldx = locx + self.oldy = locy + else: + log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " + "The loaded Excellon file has no drills ...") + self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...') + return 'fail' + + log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance)) + else: + self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.") + return 'fail' + else: + log.debug("Using Travelling Salesman drill path optimization.") + for tool in tools: + if exobj.drills: + self.tool = tool + self.postdata['toolC'] = exobj.tools[tool]["C"] # Only if tool has points. if tool in points: # Tool change sequence (optional) if toolchange: - gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy)) + gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy)) gcode += self.doformat(p.spindle_code) # Spindle start) if self.dwell is True: gcode += self.doformat(p.dwell_code) # Dwell time @@ -4812,52 +4861,23 @@ class CNCjob(Geometry): gcode += self.doformat(p.dwell_code) # Dwell time # Drillling! - for k in node_list: - locx = locations[k][0] - locy = locations[k][1] - gcode += self.doformat(p.rapid_code, x=locx, y=locy) - gcode += self.doformat(p.down_code, x=locx, y=locy) - gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) - gcode += self.doformat(p.lift_code, x=locx, y=locy) - measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy)) - self.oldx = locx - self.oldy = locy - log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance)) - else: - self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.") - return - else: - log.debug("Using Travelling Salesman drill path optimization.") - for tool in tools: - self.tool = tool - self.postdata['toolC'] = exobj.tools[tool]["C"] + altPoints = [] + for point in points[tool]: + altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0])) - # Only if tool has points. - if tool in points: - # Tool change sequence (optional) - if toolchange: - gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy)) - gcode += self.doformat(p.spindle_code) # Spindle start) - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time + for point in self.optimized_travelling_salesman(altPoints): + gcode += self.doformat(p.rapid_code, x=point[0], y=point[1]) + gcode += self.doformat(p.down_code, x=point[0], y=point[1]) + gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1]) + gcode += self.doformat(p.lift_code, x=point[0], y=point[1]) + measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy)) + self.oldx = point[0] + self.oldy = point[1] else: - gcode += self.doformat(p.spindle_code) - if self.dwell is True: - gcode += self.doformat(p.dwell_code) # Dwell time - - # Drillling! - altPoints = [] - for point in points[tool]: - altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0])) - - for point in self.optimized_travelling_salesman(altPoints): - gcode += self.doformat(p.rapid_code, x=point[0], y=point[1]) - gcode += self.doformat(p.down_code, x=point[0], y=point[1]) - gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1]) - gcode += self.doformat(p.lift_code, x=point[0], y=point[1]) - measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy)) - self.oldx = point[0] - self.oldy = point[1] + log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> " + "The loaded Excellon file has no drills ...") + self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...') + return 'fail' log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance)) gcode += self.doformat(p.spindle_stop_code) # Spindle stop @@ -4867,6 +4887,7 @@ class CNCjob(Geometry): log.debug("The total travel distance including travel to end position is: %s" % str(measured_distance) + '\n') self.gcode = gcode + return 'OK' def generate_from_multitool_geometry(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0, From 28f11ef55f935fa1b8ae1e878ebf14f8f46652a7 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 14:27:11 +0200 Subject: [PATCH 04/27] - small bugs related to shortcut keys solved --- FlatCAMGUI.py | 7 +++++-- ObjectCollection.py | 2 +- README.md | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index b6a39a53..1a31138c 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1508,6 +1508,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if event.key() == QtCore.Qt.Key_3: self.app.on_select_tab('tool') + if event.key() == QtCore.Qt.Key_G: + self.grid_snap_btn.trigger() + if event.key == QtCore.Qt.Key_Q: self.app.on_toggle_units_click() @@ -3726,7 +3729,7 @@ class FlatCAMInfoBar(QtWidgets.QWidget): layout.addWidget(self.icon) self.text = QtWidgets.QLabel(self) - self.text.setText("Hello!") + self.text.setText("Application started ...") self.text.setToolTip("Hello!") layout.addWidget(self.text) @@ -3742,7 +3745,7 @@ class FlatCAMInfoBar(QtWidgets.QWidget): self.pmap.fill() if level == "ERROR" or level == "ERROR_NOTCL": self.pmap = QtGui.QPixmap('share/redlight12.png') - elif level == "success": + elif level == "success" or level == "SUCCESS": self.pmap = QtGui.QPixmap('share/greenlight12.png') elif level == "WARNING" or level == "WARNING_NOTCL": self.pmap = QtGui.QPixmap('share/yellowlight12.png') diff --git a/ObjectCollection.py b/ObjectCollection.py index f9530f6f..7107644f 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -417,7 +417,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): # Grid toggle if key == QtCore.Qt.Key_G: - self.app.geo_editor.grid_snap_btn.trigger() + self.app.ui.grid_snap_btn.trigger() # Jump to coords if key == QtCore.Qt.Key_J: diff --git a/README.md b/README.md index 84448839..4699e53e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ CAD program, and create G-Code for Isolation routing. - fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. - made the shell toggle shortcut key work when focused on Selected Tab; toggle units shortcut also -- changed the messages for Units COnversion +- changed the messages for Units Conversion 5.02.3019 From 67d089832e2e0b9402ee00ddafc86f6f35b70ab0 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 14:44:16 +0200 Subject: [PATCH 05/27] - all kwy shortcuts work across the entire application; started to move all the shortcuts definitions in FlatCAMGUI.keyPressEvent() --- FlatCAMGUI.py | 248 +++++++++++++++++++++-- ObjectCollection.py | 471 ++++++++++++++++++++++---------------------- README.md | 2 +- 3 files changed, 469 insertions(+), 252 deletions(-) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 1a31138c..4f853936 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -10,6 +10,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5.QtCore import Qt, QSettings from GUIElements import * import platform +import webbrowser class FlatCAMGUI(QtWidgets.QMainWindow): @@ -1498,31 +1499,244 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.snap_magnet.setDisabled(True) def keyPressEvent(self, event): + modifiers = QtWidgets.QApplication.keyboardModifiers() + active = self.app.collection.get_active() + selected = self.app.collection.get_selected() - if event.key() == QtCore.Qt.Key_1: - self.app.on_select_tab('project') + # events out of the self.app.collection view (it's about Project Tab) are of type int + if type(event) is int: + key = event + # events from the GUI are of type QKeyEvent + else: + key = event.key() - if event.key() == QtCore.Qt.Key_2: - self.app.on_select_tab('selected') + if modifiers == QtCore.Qt.ControlModifier: + if key == QtCore.Qt.Key_A: + self.app.on_selectall() - if event.key() == QtCore.Qt.Key_3: - self.app.on_select_tab('tool') + if key == QtCore.Qt.Key_C: + self.app.on_copy_object() - if event.key() == QtCore.Qt.Key_G: - self.grid_snap_btn.trigger() + if key == QtCore.Qt.Key_E: + self.app.on_fileopenexcellon() - if event.key == QtCore.Qt.Key_Q: - self.app.on_toggle_units_click() + if key == QtCore.Qt.Key_G: + self.app.on_fileopengerber() - if event.key() == QtCore.Qt.Key_S: - self.app.on_toggle_shell() + if key == QtCore.Qt.Key_N: + self.app.on_file_new_click() - # Show shortcut list - if event.key() == QtCore.Qt.Key_Ampersand: - self.app.on_shortcut_list() + if key == QtCore.Qt.Key_M: + self.app.measurement_tool.run() + if key == QtCore.Qt.Key_O: + self.app.on_file_openproject() - if event.key() == QtCore.Qt.Key_QuoteLeft: - self.app.on_shortcut_list() + if key == QtCore.Qt.Key_S: + self.app.on_file_saveproject() + + # Toggle Plot Area + if key == QtCore.Qt.Key_F10: + self.app.on_toggle_plotarea() + + return + elif modifiers == QtCore.Qt.ShiftModifier: + + # Copy Object Name + # Copy Object Name + if key == QtCore.Qt.Key_C: + self.app.on_copy_name() + + # Toggle axis + if key == QtCore.Qt.Key_G: + if self.toggle_axis is False: + self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) + self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) + self.app.plotcanvas.redraw() + self.app.toggle_axis = True + else: + self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) + + self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) + self.appplotcanvas.redraw() + self.app.toggle_axis = False + + # Open Preferences Window + if key == QtCore.Qt.Key_P: + self.app.on_preferences() + return + + # Rotate Object by 90 degree CCW + if key == QtCore.Qt.Key_R: + self.app.on_rotate(silent=True, preset=-90) + return + + # Run a Script + if key == QtCore.Qt.Key_S: + self.app.on_filerunscript() + return + + # Toggle Workspace + if key == QtCore.Qt.Key_W: + self.app.on_workspace_menu() + return + + # Skew on X axis + if key == QtCore.Qt.Key_X: + self.app.on_skewx() + return + + # Skew on Y axis + if key == QtCore.Qt.Key_Y: + self.app.on_skewy() + return + + elif modifiers == QtCore.Qt.AltModifier: + # Eanble all plots + if key == Qt.Key_1: + self.app.enable_all_plots() + + # Disable all plots + if key == Qt.Key_2: + self.app.disable_all_plots() + + # Disable all other plots + if key == Qt.Key_3: + self.app.disable_other_plots() + + # 2-Sided PCB Tool + if key == QtCore.Qt.Key_D: + self.app.dblsidedtool.run() + return + + # Non-Copper Clear Tool + if key == QtCore.Qt.Key_N: + self.app.ncclear_tool.run() + return + + # Transformation Tool + if key == QtCore.Qt.Key_R: + self.app.transform_tool.run() + return + + # Cutout Tool + if key == QtCore.Qt.Key_U: + self.app.cutout_tool.run() + return + + else: + # Open Manual + if key == QtCore.Qt.Key_F1: + webbrowser.open(self.app.manual_url) + + # Open Video Help + if key == QtCore.Qt.Key_F2: + webbrowser.open(self.app.video_url) + + # Switch to Project Tab + if key == QtCore.Qt.Key_1: + self.app.on_select_tab('project') + + # Switch to Selected Tab + if key == QtCore.Qt.Key_2: + self.app.on_select_tab('selected') + + # Switch to Tool Tab + if key == QtCore.Qt.Key_3: + self.app.on_select_tab('tool') + + # Delete + if key == QtCore.Qt.Key_Delete and active: + # Delete via the application to + # ensure cleanup of the GUI + active.app.on_delete() + + # Space = Toggle Active/Inactive + if key == QtCore.Qt.Key_Space: + for select in selected: + select.ui.plot_cb.toggle() + self.app.delete_selection_shape() + + # Copy Object Name + if key == QtCore.Qt.Key_E: + self.app.object2editor() + + # Grid toggle + if key == QtCore.Qt.Key_G: + self.app.ui.grid_snap_btn.trigger() + + # Jump to coords + if key == QtCore.Qt.Key_J: + self.app.on_jump_to() + + # New Excellon + if key == QtCore.Qt.Key_L: + self.app.new_excellon_object() + + # Move tool toggle + if key == QtCore.Qt.Key_M: + self.app.move_tool.toggle() + + # New Geometry + if key == QtCore.Qt.Key_N: + self.app.on_new_geometry() + + # Set Origin + if key == QtCore.Qt.Key_O: + self.app.on_set_origin() + return + + # Set Origin + if key == QtCore.Qt.Key_P: + self.app.properties_tool.run() + return + + # Change Units + if key == QtCore.Qt.Key_Q: + if self.app.options["units"] == 'MM': + self.app.general_options_form.general_app_group.units_radio.set_value("IN") + else: + self.app.general_options_form.general_app_group.units_radio.set_value("MM") + self.app.on_toggle_units() + + # Rotate Object by 90 degree CW + if key == QtCore.Qt.Key_R: + self.app.on_rotate(silent=True, preset=90) + + # Shell toggle + if key == QtCore.Qt.Key_S: + self.app.on_toggle_shell() + + # Transform Tool + if key == QtCore.Qt.Key_T: + self.app.transform_tool.run() + + # Zoom Fit + if key == QtCore.Qt.Key_V: + self.app.on_zoom_fit(None) + + # Mirror on X the selected object(s) + if key == QtCore.Qt.Key_X: + self.app.on_flipx() + + # Mirror on Y the selected object(s) + if key == QtCore.Qt.Key_Y: + self.app.on_flipy() + + # Zoom In + if key == QtCore.Qt.Key_Equal: + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse) + + # Zoom Out + if key == QtCore.Qt.Key_Minus: + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) + + # Show shortcut list + if key == QtCore.Qt.Key_Ampersand: + self.app.on_shortcut_list() + + if key == QtCore.Qt.Key_QuoteLeft: + self.app.on_shortcut_list() + return def dragEnterEvent(self, event): if event.mimeData().hasUrls: diff --git a/ObjectCollection.py b/ObjectCollection.py index 7107644f..9bcd89c2 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -12,7 +12,7 @@ import inspect # TODO: Remove import FlatCAMApp from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5.QtCore import Qt -import webbrowser +# import webbrowser class KeySensitiveListView(QtWidgets.QTreeView): @@ -228,6 +228,8 @@ class ObjectCollection(QtCore.QAbstractItemModel): # tasks know that they have to wait until available. self.promises = set() + self.app = app + ### View self.view = KeySensitiveListView(app) self.view.setModel(self) @@ -247,7 +249,8 @@ class ObjectCollection(QtCore.QAbstractItemModel): ## GUI Events self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change) self.view.activated.connect(self.on_item_activated) - self.view.keyPressed.connect(self.on_key) + # self.view.keyPressed.connect(self.on_key) + self.view.keyPressed.connect(self.app.ui.keyPressEvent) self.view.clicked.connect(self.on_mouse_down) self.view.customContextMenuRequested.connect(self.on_menu_request) @@ -260,238 +263,238 @@ class ObjectCollection(QtCore.QAbstractItemModel): def has_promises(self): return len(self.promises) > 0 - def on_key(self, key): - modifiers = QtWidgets.QApplication.keyboardModifiers() - active = self.get_active() - selected = self.get_selected() - - if modifiers == QtCore.Qt.ControlModifier: - if key == QtCore.Qt.Key_A: - self.app.on_selectall() - - if key == QtCore.Qt.Key_C: - self.app.on_copy_object() - - if key == QtCore.Qt.Key_E: - self.app.on_fileopenexcellon() - - if key == QtCore.Qt.Key_G: - self.app.on_fileopengerber() - - if key == QtCore.Qt.Key_N: - self.app.on_file_new_click() - - if key == QtCore.Qt.Key_M: - self.app.measurement_tool.run() - if key == QtCore.Qt.Key_O: - self.app.on_file_openproject() - - if key == QtCore.Qt.Key_S: - self.app.on_file_saveproject() - - # Toggle Plot Area - if key == QtCore.Qt.Key_F10: - self.app.on_toggle_plotarea() - - return - elif modifiers == QtCore.Qt.ShiftModifier: - - # Copy Object Name - # Copy Object Name - if key == QtCore.Qt.Key_C: - self.app.on_copy_name() - - # Toggle axis - if key == QtCore.Qt.Key_G: - if self.toggle_axis is False: - self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) - self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) - self.app.plotcanvas.redraw() - self.app.toggle_axis = True - else: - self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) - - self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) - self.appplotcanvas.redraw() - self.app.toggle_axis = False - - # Open Preferences Window - if key == QtCore.Qt.Key_P: - self.app.on_preferences() - return - - # Rotate Object by 90 degree CCW - if key == QtCore.Qt.Key_R: - self.app.on_rotate(silent=True, preset=-90) - return - - # Run a Script - if key == QtCore.Qt.Key_S: - self.app.on_filerunscript() - return - - # Toggle Workspace - if key == QtCore.Qt.Key_W: - self.app.on_workspace_menu() - return - - # Skew on X axis - if key == QtCore.Qt.Key_X: - self.app.on_skewx() - return - - # Skew on Y axis - if key == QtCore.Qt.Key_Y: - self.app.on_skewy() - return - - elif modifiers == QtCore.Qt.AltModifier: - # Eanble all plots - if key == Qt.Key_1: - self.app.enable_all_plots() - - # Disable all plots - if key == Qt.Key_2: - self.app.disable_all_plots() - - # Disable all other plots - if key == Qt.Key_3: - self.app.disable_other_plots() - - # 2-Sided PCB Tool - if key == QtCore.Qt.Key_D: - self.app.dblsidedtool.run() - return - - # Non-Copper Clear Tool - if key == QtCore.Qt.Key_N: - self.app.ncclear_tool.run() - return - - # Transformation Tool - if key == QtCore.Qt.Key_R: - self.app.transform_tool.run() - return - - # Cutout Tool - if key == QtCore.Qt.Key_U: - self.app.cutout_tool.run() - return - - else: - # Open Manual - if key == QtCore.Qt.Key_F1: - webbrowser.open(self.app.manual_url) - - # Open Video Help - if key == QtCore.Qt.Key_F2: - webbrowser.open(self.app.video_url) - - # Switch to Project Tab - if key == QtCore.Qt.Key_1: - self.app.on_select_tab('project') - - # Switch to Selected Tab - if key == QtCore.Qt.Key_2: - self.app.on_select_tab('selected') - - # Switch to Tool Tab - if key == QtCore.Qt.Key_3: - self.app.on_select_tab('tool') - - # Delete - if key == QtCore.Qt.Key_Delete and active: - # Delete via the application to - # ensure cleanup of the GUI - active.app.on_delete() - - # Space = Toggle Active/Inactive - if key == QtCore.Qt.Key_Space: - for select in selected: - select.ui.plot_cb.toggle() - self.app.delete_selection_shape() - - # Copy Object Name - if key == QtCore.Qt.Key_E: - self.app.object2editor() - - # Grid toggle - if key == QtCore.Qt.Key_G: - self.app.ui.grid_snap_btn.trigger() - - # Jump to coords - if key == QtCore.Qt.Key_J: - self.app.on_jump_to() - - # New Excellon - if key == QtCore.Qt.Key_L: - self.app.new_excellon_object() - - # Move tool toggle - if key == QtCore.Qt.Key_M: - self.app.move_tool.toggle() - - # New Geometry - if key == QtCore.Qt.Key_N: - self.app.on_new_geometry() - - # Set Origin - if key == QtCore.Qt.Key_O: - self.app.on_set_origin() - return - - # Set Origin - if key == QtCore.Qt.Key_P: - self.app.properties_tool.run() - return - - # Change Units - if key == QtCore.Qt.Key_Q: - if self.app.options["units"] == 'MM': - self.app.general_options_form.general_app_group.units_radio.set_value("IN") - else: - self.app.general_options_form.general_app_group.units_radio.set_value("MM") - self.app.on_toggle_units() - - # Rotate Object by 90 degree CW - if key == QtCore.Qt.Key_R: - self.app.on_rotate(silent=True, preset=90) - - # Shell toggle - if key == QtCore.Qt.Key_S: - self.app.on_toggle_shell() - - # Transform Tool - if key == QtCore.Qt.Key_T: - self.app.transform_tool.run() - - # Zoom Fit - if key == QtCore.Qt.Key_V: - self.app.on_zoom_fit(None) - - # Mirror on X the selected object(s) - if key == QtCore.Qt.Key_X: - self.app.on_flipx() - - # Mirror on Y the selected object(s) - if key == QtCore.Qt.Key_Y: - self.app.on_flipy() - - # Zoom In - if key == QtCore.Qt.Key_Equal: - self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse) - - # Zoom Out - if key == QtCore.Qt.Key_Minus: - self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) - - # Show shortcut list - if key == QtCore.Qt.Key_Ampersand: - self.app.on_shortcut_list() - - if key == QtCore.Qt.Key_QuoteLeft: - self.app.on_shortcut_list() - return + # def on_key(self, key): + # modifiers = QtWidgets.QApplication.keyboardModifiers() + # active = self.get_active() + # selected = self.get_selected() + # + # if modifiers == QtCore.Qt.ControlModifier: + # if key == QtCore.Qt.Key_A: + # self.app.on_selectall() + # + # if key == QtCore.Qt.Key_C: + # self.app.on_copy_object() + # + # if key == QtCore.Qt.Key_E: + # self.app.on_fileopenexcellon() + # + # if key == QtCore.Qt.Key_G: + # self.app.on_fileopengerber() + # + # if key == QtCore.Qt.Key_N: + # self.app.on_file_new_click() + # + # if key == QtCore.Qt.Key_M: + # self.app.measurement_tool.run() + # if key == QtCore.Qt.Key_O: + # self.app.on_file_openproject() + # + # if key == QtCore.Qt.Key_S: + # self.app.on_file_saveproject() + # + # # Toggle Plot Area + # if key == QtCore.Qt.Key_F10: + # self.app.on_toggle_plotarea() + # + # return + # elif modifiers == QtCore.Qt.ShiftModifier: + # + # # Copy Object Name + # # Copy Object Name + # if key == QtCore.Qt.Key_C: + # self.app.on_copy_name() + # + # # Toggle axis + # if key == QtCore.Qt.Key_G: + # if self.toggle_axis is False: + # self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) + # self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) + # self.app.plotcanvas.redraw() + # self.app.toggle_axis = True + # else: + # self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) + # + # self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) + # self.appplotcanvas.redraw() + # self.app.toggle_axis = False + # + # # Open Preferences Window + # if key == QtCore.Qt.Key_P: + # self.app.on_preferences() + # return + # + # # Rotate Object by 90 degree CCW + # if key == QtCore.Qt.Key_R: + # self.app.on_rotate(silent=True, preset=-90) + # return + # + # # Run a Script + # if key == QtCore.Qt.Key_S: + # self.app.on_filerunscript() + # return + # + # # Toggle Workspace + # if key == QtCore.Qt.Key_W: + # self.app.on_workspace_menu() + # return + # + # # Skew on X axis + # if key == QtCore.Qt.Key_X: + # self.app.on_skewx() + # return + # + # # Skew on Y axis + # if key == QtCore.Qt.Key_Y: + # self.app.on_skewy() + # return + # + # elif modifiers == QtCore.Qt.AltModifier: + # # Eanble all plots + # if key == Qt.Key_1: + # self.app.enable_all_plots() + # + # # Disable all plots + # if key == Qt.Key_2: + # self.app.disable_all_plots() + # + # # Disable all other plots + # if key == Qt.Key_3: + # self.app.disable_other_plots() + # + # # 2-Sided PCB Tool + # if key == QtCore.Qt.Key_D: + # self.app.dblsidedtool.run() + # return + # + # # Non-Copper Clear Tool + # if key == QtCore.Qt.Key_N: + # self.app.ncclear_tool.run() + # return + # + # # Transformation Tool + # if key == QtCore.Qt.Key_R: + # self.app.transform_tool.run() + # return + # + # # Cutout Tool + # if key == QtCore.Qt.Key_U: + # self.app.cutout_tool.run() + # return + # + # else: + # # Open Manual + # if key == QtCore.Qt.Key_F1: + # webbrowser.open(self.app.manual_url) + # + # # Open Video Help + # if key == QtCore.Qt.Key_F2: + # webbrowser.open(self.app.video_url) + # + # # Switch to Project Tab + # if key == QtCore.Qt.Key_1: + # self.app.on_select_tab('project') + # + # # Switch to Selected Tab + # if key == QtCore.Qt.Key_2: + # self.app.on_select_tab('selected') + # + # # Switch to Tool Tab + # if key == QtCore.Qt.Key_3: + # self.app.on_select_tab('tool') + # + # # Delete + # if key == QtCore.Qt.Key_Delete and active: + # # Delete via the application to + # # ensure cleanup of the GUI + # active.app.on_delete() + # + # # Space = Toggle Active/Inactive + # if key == QtCore.Qt.Key_Space: + # for select in selected: + # select.ui.plot_cb.toggle() + # self.app.delete_selection_shape() + # + # # Copy Object Name + # if key == QtCore.Qt.Key_E: + # self.app.object2editor() + # + # # Grid toggle + # if key == QtCore.Qt.Key_G: + # self.app.ui.grid_snap_btn.trigger() + # + # # Jump to coords + # if key == QtCore.Qt.Key_J: + # self.app.on_jump_to() + # + # # New Excellon + # if key == QtCore.Qt.Key_L: + # self.app.new_excellon_object() + # + # # Move tool toggle + # if key == QtCore.Qt.Key_M: + # self.app.move_tool.toggle() + # + # # New Geometry + # if key == QtCore.Qt.Key_N: + # self.app.on_new_geometry() + # + # # Set Origin + # if key == QtCore.Qt.Key_O: + # self.app.on_set_origin() + # return + # + # # Set Origin + # if key == QtCore.Qt.Key_P: + # self.app.properties_tool.run() + # return + # + # # Change Units + # if key == QtCore.Qt.Key_Q: + # if self.app.options["units"] == 'MM': + # self.app.general_options_form.general_app_group.units_radio.set_value("IN") + # else: + # self.app.general_options_form.general_app_group.units_radio.set_value("MM") + # self.app.on_toggle_units() + # + # # Rotate Object by 90 degree CW + # if key == QtCore.Qt.Key_R: + # self.app.on_rotate(silent=True, preset=90) + # + # # Shell toggle + # if key == QtCore.Qt.Key_S: + # self.app.on_toggle_shell() + # + # # Transform Tool + # if key == QtCore.Qt.Key_T: + # self.app.transform_tool.run() + # + # # Zoom Fit + # if key == QtCore.Qt.Key_V: + # self.app.on_zoom_fit(None) + # + # # Mirror on X the selected object(s) + # if key == QtCore.Qt.Key_X: + # self.app.on_flipx() + # + # # Mirror on Y the selected object(s) + # if key == QtCore.Qt.Key_Y: + # self.app.on_flipy() + # + # # Zoom In + # if key == QtCore.Qt.Key_Equal: + # self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse) + # + # # Zoom Out + # if key == QtCore.Qt.Key_Minus: + # self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) + # + # # Show shortcut list + # if key == QtCore.Qt.Key_Ampersand: + # self.app.on_shortcut_list() + # + # if key == QtCore.Qt.Key_QuoteLeft: + # self.app.on_shortcut_list() + # return def on_mouse_down(self, event): FlatCAMApp.App.log.debug("Mouse button pressed on list") diff --git a/README.md b/README.md index 4699e53e..7acd5123 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ CAD program, and create G-Code for Isolation routing. - fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it. - fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. -- made the shell toggle shortcut key work when focused on Selected Tab; toggle units shortcut also - changed the messages for Units Conversion +- all kwy shortcuts work across the entire application; started to move all the shortcuts definitions in FlatCAMGUI.keyPressEvent() 5.02.3019 From 8bbb9ba534f1755a82a058d39a8a72011eb21bd0 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 16:23:09 +0200 Subject: [PATCH 06/27] - all kwy shortcuts work across the entire application; started to move all the shortcuts definitions in FlatCAMGUI.keyPressEvent() --- FlatCAMApp.py | 438 ++++++++++++++++---------------- FlatCAMEditor.py | 21 +- FlatCAMGUI.py | 29 ++- README.md | 2 +- flatcamTools/ToolMeasurement.py | 9 +- flatcamTools/ToolMove.py | 2 +- 6 files changed, 261 insertions(+), 240 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 151e50a5..408b23f6 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -964,8 +964,7 @@ class App(QtCore.QObject): self.plotcanvas.vis_connect('mouse_double_click', self.on_double_click_over_plot) # Keys over plot enabled - self.plotcanvas.vis_connect('key_press', self.on_key_over_plot) - self.plotcanvas.vis_connect('key_release', self.on_key_release_over_plot) + self.plotcanvas.vis_connect('key_press', self.ui.keyPressEvent) self.ui.splitter.setStretchFactor(1, 2) @@ -3934,223 +3933,224 @@ class App(QtCore.QObject): else: return 0 - def on_key_over_plot(self, event): - """ - Callback for the key pressed event when the canvas is focused. Keyboard - shortcuts are handled here. So far, these are the shortcuts: - - ========== ============================================ - Key Action - ========== ============================================ - '1' Zoom-fit. Fits the axes limits to the data. - '2' Zoom-out. - '3' Zoom-in. - 'ctrl+m' Toggle on-off the measuring tool. - ========== ============================================ - - :param event: Ignored. - :return: None - """ - - self.key_modifiers = QtWidgets.QApplication.keyboardModifiers() - - if self.key_modifiers == QtCore.Qt.ControlModifier: - if event.key == 'A': - self.on_selectall() - - if event.key == 'C': - self.on_copy_object() - - if event.key == 'E': - self.on_fileopenexcellon() - if event.key == 'G': - self.on_fileopengerber() - - if event.key == 'N': - self.on_file_new_click() - - if event.key == 'M': - self.measurement_tool.run() - - if event.key == 'O': - self.on_file_openproject() - - if event.key == 'S': - self.on_file_saveproject() - - # Toggle Plot Area - if event.key == 'F10': - self.on_toggle_plotarea() - - return - elif self.key_modifiers == QtCore.Qt.AltModifier: - # place holder for further shortcut key - - if event.key == '1': - self.enable_all_plots() - - if event.key == '2': - self.disable_all_plots() - - if event.key == '3': - self.disable_other_plots() - - if event.key == 'C': - self.calculator_tool.run() - - if event.key == 'D': - self.dblsidedtool.run() - - if event.key == 'L': - self.film_tool.run() - - if event.key == 'N': - self.ncclear_tool.run() - - if event.key == 'P': - self.paint_tool.run() - - if event.key == 'R': - self.transform_tool.run() - - if event.key == 'U': - self.cutout_tool.run() - - if event.key == 'Z': - self.panelize_tool.run() - - if event.key == 'F10': - self.on_fullscreen() - - return - elif self.key_modifiers == QtCore.Qt.ShiftModifier: - # place holder for further shortcut key - - if event.key == 'C': - self.on_copy_name() - - # Toggle axis - if event.key == 'G': - self.on_toggle_axis() - - # Open Preferences Window - if event.key == 'P': - self.on_preferences() - - # Rotate Object by 90 degree CCW - if event.key == 'R': - self.on_rotate(silent=True, preset=-90) - - # Run a Script - if event.key == 'S': - self.on_filerunscript() - - # Toggle Workspace - if event.key == 'W': - self.on_workspace_menu() - - # Skew on X axis - if event.key == 'X': - self.on_skewx() - - # Skew on Y axis - if event.key == 'Y': - self.on_skewy() - - else: - if event.key == 'F1': - webbrowser.open(self.manual_url) - return - - if event.key == 'F2': - webbrowser.open(self.video_url) - return - - if event.key == self.defaults['zoom_out_key']: # '-' - self.plotcanvas.zoom(1 / self.defaults['zoom_ratio'], self.mouse) - return - - if event.key == self.defaults['zoom_in_key']: # '=' - self.plotcanvas.zoom(self.defaults['zoom_ratio'], self.mouse) - return - - if event.key == 'Delete': - self.on_delete() - return - - if event.key == 'Space': - if self.collection.get_active() is not None: - self.collection.get_active().ui.plot_cb.toggle() - self.delete_selection_shape() - - if event.key == '1': - self.on_select_tab('project') - - if event.key == '2': - self.on_select_tab('selected') - - if event.key == '3': - self.on_select_tab('tool') - - if event.key == 'E': - self.object2editor() - - if event.key == self.defaults['grid_toggle_key']: # G - self.ui.grid_snap_btn.trigger() - - if event.key == 'J': - self.on_jump_to() - - if event.key == 'L': - self.new_excellon_object() - - if event.key == 'M': - self.move_tool.toggle() - return - - if event.key == 'N': - self.on_new_geometry() - - if event.key == 'O': - self.on_set_origin() - - if event.key == 'P': - self.properties_tool.run() - - if event.key == 'Q': - self.on_toggle_units_click() - - if event.key == 'R': - self.on_rotate(silent=True, preset=90) - - if event.key == 'S': - self.on_toggle_shell() - - if event.key == 'V': - self.on_zoom_fit(None) - - if event.key == 'X': - self.on_flipx() - - if event.key == 'Y': - self.on_flipy() - - if event.key == '`': - self.on_shortcut_list() - - def on_key_release_over_plot(self, event): - modifiers = QtWidgets.QApplication.keyboardModifiers() - - if modifiers == QtCore.Qt.ControlModifier: - return - elif modifiers == QtCore.Qt.AltModifier: - # place holder for further shortcut key - return - elif modifiers == QtCore.Qt.ShiftModifier: - # place holder for further shortcut key - return - else: - return + # def on_key_over_plot(self, event): + # """ + # Callback for the key pressed event when the canvas is focused. Keyboard + # shortcuts are handled here. So far, these are the shortcuts: + # + # ========== ============================================ + # Key Action + # ========== ============================================ + # '1' Zoom-fit. Fits the axes limits to the data. + # '2' Zoom-out. + # '3' Zoom-in. + # 'ctrl+m' Toggle on-off the measuring tool. + # ========== ============================================ + # + # :param event: Ignored. + # :return: None + # """ + # print(type(event.key), event.key) + # self.key_modifiers = QtWidgets.QApplication.keyboardModifiers() + # + # if self.key_modifiers == QtCore.Qt.ControlModifier: + # if event.key == 'A': + # self.on_selectall() + # + # if event.key == 'C': + # self.on_copy_object() + # + # if event.key == 'E': + # self.on_fileopenexcellon() + # + # if event.key == 'G': + # self.on_fileopengerber() + # + # if event.key == 'N': + # self.on_file_new_click() + # + # if event.key == 'M': + # self.measurement_tool.run() + # + # if event.key == 'O': + # self.on_file_openproject() + # + # if event.key == 'S': + # self.on_file_saveproject() + # + # # Toggle Plot Area + # if event.key == 'F10': + # self.on_toggle_plotarea() + # + # return + # elif self.key_modifiers == QtCore.Qt.AltModifier: + # # place holder for further shortcut key + # + # if event.key == '1': + # self.enable_all_plots() + # + # if event.key == '2': + # self.disable_all_plots() + # + # if event.key == '3': + # self.disable_other_plots() + # + # if event.key == 'C': + # self.calculator_tool.run() + # + # if event.key == 'D': + # self.dblsidedtool.run() + # + # if event.key == 'L': + # self.film_tool.run() + # + # if event.key == 'N': + # self.ncclear_tool.run() + # + # if event.key == 'P': + # self.paint_tool.run() + # + # if event.key == 'R': + # self.transform_tool.run() + # + # if event.key == 'U': + # self.cutout_tool.run() + # + # if event.key == 'Z': + # self.panelize_tool.run() + # + # if event.key == 'F10': + # self.on_fullscreen() + # + # return + # elif self.key_modifiers == QtCore.Qt.ShiftModifier: + # # place holder for further shortcut key + # + # if event.key == 'C': + # self.on_copy_name() + # + # # Toggle axis + # if event.key == 'G': + # self.on_toggle_axis() + # + # # Open Preferences Window + # if event.key == 'P': + # self.on_preferences() + # + # # Rotate Object by 90 degree CCW + # if event.key == 'R': + # self.on_rotate(silent=True, preset=-90) + # + # # Run a Script + # if event.key == 'S': + # self.on_filerunscript() + # + # # Toggle Workspace + # if event.key == 'W': + # self.on_workspace_menu() + # + # # Skew on X axis + # if event.key == 'X': + # self.on_skewx() + # + # # Skew on Y axis + # if event.key == 'Y': + # self.on_skewy() + # + # else: + # if event.key == 'F1': + # webbrowser.open(self.manual_url) + # return + # + # if event.key == 'F2': + # webbrowser.open(self.video_url) + # return + # + # if event.key == self.defaults['zoom_out_key']: # '-' + # self.plotcanvas.zoom(1 / self.defaults['zoom_ratio'], self.mouse) + # return + # + # if event.key == self.defaults['zoom_in_key']: # '=' + # self.plotcanvas.zoom(self.defaults['zoom_ratio'], self.mouse) + # return + # + # if event.key == 'Delete': + # self.on_delete() + # return + # + # if event.key == 'Space': + # if self.collection.get_active() is not None: + # self.collection.get_active().ui.plot_cb.toggle() + # self.delete_selection_shape() + # + # if event.key == '1': + # self.on_select_tab('project') + # + # if event.key == '2': + # self.on_select_tab('selected') + # + # if event.key == '3': + # self.on_select_tab('tool') + # + # if event.key == 'E': + # self.object2editor() + # + # if event.key == self.defaults['grid_toggle_key']: # G + # self.ui.grid_snap_btn.trigger() + # + # if event.key == 'J': + # self.on_jump_to() + # + # if event.key == 'L': + # self.new_excellon_object() + # + # if event.key == 'M': + # self.move_tool.toggle() + # return + # + # if event.key == 'N': + # self.on_new_geometry() + # + # if event.key == 'O': + # self.on_set_origin() + # + # if event.key == 'P': + # self.properties_tool.run() + # + # if event.key == 'Q': + # self.on_toggle_units_click() + # + # if event.key == 'R': + # self.on_rotate(silent=True, preset=90) + # + # if event.key == 'S': + # self.on_toggle_shell() + # + # if event.key == 'V': + # self.on_zoom_fit(None) + # + # if event.key == 'X': + # self.on_flipx() + # + # if event.key == 'Y': + # self.on_flipy() + # + # if event.key == '`': + # self.on_shortcut_list() + # + # def on_key_release_over_plot(self, event): + # modifiers = QtWidgets.QApplication.keyboardModifiers() + # + # if modifiers == QtCore.Qt.ControlModifier: + # return + # elif modifiers == QtCore.Qt.AltModifier: + # # place holder for further shortcut key + # return + # elif modifiers == QtCore.Qt.ShiftModifier: + # # place holder for further shortcut key + # return + # else: + # return def on_shortcut_list(self): self.report_usage("on_shortcut_list()") diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index a472da6b..84cb50d7 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -2134,19 +2134,20 @@ class FlatCAMGeoEditor(QtCore.QObject): # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp # but those from FlatCAMGeoEditor - self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot) + + self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot) - self.app.collection.view.keyPressed.disconnect() + self.app.collection.view.clicked.disconnect() self.canvas.vis_connect('mouse_press', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) self.canvas.vis_connect('key_press', self.on_canvas_key) - self.canvas.vis_connect('key_release', self.on_canvas_key_release) + def disconnect_canvas_event_handlers(self): @@ -2154,15 +2155,14 @@ class FlatCAMGeoEditor(QtCore.QObject): self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) self.canvas.vis_disconnect('key_press', self.on_canvas_key) - self.canvas.vis_disconnect('key_release', self.on_canvas_key_release) + # we restore the key and mouse control to FlatCAMApp method - self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot) + self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot) - self.app.collection.view.keyPressed.connect(self.app.collection.on_key) self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down) def add_shape(self, shape): @@ -4277,7 +4277,7 @@ class FlatCAMExcEditor(QtCore.QObject): # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp # but those from FlatCAMGeoEditor - self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot) + self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) @@ -4289,10 +4289,9 @@ class FlatCAMExcEditor(QtCore.QObject): self.canvas.vis_connect('mouse_move', self.on_canvas_move) self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) self.canvas.vis_connect('key_press', self.on_canvas_key) - self.canvas.vis_connect('key_release', self.on_canvas_key_release) + def disconnect_canvas_event_handlers(self): - self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) @@ -4300,12 +4299,12 @@ class FlatCAMExcEditor(QtCore.QObject): self.canvas.vis_disconnect('key_release', self.on_canvas_key_release) # we restore the key and mouse control to FlatCAMApp method - self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot) + self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot) - self.app.collection.view.keyPressed.connect(self.app.collection.on_key) + self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down) def clear(self): diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 4f853936..beb885ea 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1507,8 +1507,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if type(event) is int: key = event # events from the GUI are of type QKeyEvent - else: + elif type(event) == QtGui.QKeyEvent: key = event.key() + # events from Vispy are of type KeyEvent + else: + key = event.key if modifiers == QtCore.Qt.ControlModifier: if key == QtCore.Qt.Key_A: @@ -1528,6 +1531,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_M: self.app.measurement_tool.run() + if key == QtCore.Qt.Key_O: self.app.on_file_openproject() @@ -1603,16 +1607,30 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == Qt.Key_3: self.app.disable_other_plots() + # Calculator Tool + if key == QtCore.Qt.Key_C: + self.app.calculator_tool.run() + # 2-Sided PCB Tool if key == QtCore.Qt.Key_D: self.app.dblsidedtool.run() return + # Film Tool + if key == QtCore.Qt.Key_L: + self.app.film_tool.run() + return + # Non-Copper Clear Tool if key == QtCore.Qt.Key_N: self.app.ncclear_tool.run() return + # Paint Tool + if key == QtCore.Qt.Key_P: + self.app.paint_tool.run() + return + # Transformation Tool if key == QtCore.Qt.Key_R: self.app.transform_tool.run() @@ -1623,6 +1641,15 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.cutout_tool.run() return + # Panelize Tool + if key == QtCore.Qt.Key_Z: + self.app.panelize_tool.run() + return + + # Toggle Fullscreen + if key == QtCore.Qt.Key_F10: + self.app.on_fullscreen() + return else: # Open Manual if key == QtCore.Qt.Key_F1: diff --git a/README.md b/README.md index 7acd5123..7941afd3 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ CAD program, and create G-Code for Isolation routing. - fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. - changed the messages for Units Conversion -- all kwy shortcuts work across the entire application; started to move all the shortcuts definitions in FlatCAMGUI.keyPressEvent() +- all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent() 5.02.3019 diff --git a/flatcamTools/ToolMeasurement.py b/flatcamTools/ToolMeasurement.py index 683bbbb0..9890f28a 100644 --- a/flatcamTools/ToolMeasurement.py +++ b/flatcamTools/ToolMeasurement.py @@ -184,25 +184,22 @@ class Measurement(FlatCAMTool): # disconnect the mouse/key events from functions of measurement tool self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move_meas) self.app.plotcanvas.vis_disconnect('mouse_press', self.on_click_meas) - self.app.plotcanvas.vis_disconnect('key_release', self.on_key_release_meas) # reconnect the mouse/key events to the functions from where the tool was called if self.app.call_source == 'app': self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot) + self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot) elif self.app.call_source == 'geo_editor': self.app.geo_editor.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move) self.app.geo_editor.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click) self.app.geo_editor.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key) - self.app.geo_editor.canvas.vis_connect('key_release', self.app.geo_editor.on_canvas_key_release) self.app.geo_editor.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release) elif self.app.call_source == 'exc_editor': self.app.exc_editor.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move) self.app.exc_editor.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click) self.app.exc_editor.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key) - self.app.exc_editor.canvas.vis_connect('key_release', self.app.exc_editor.on_canvas_key_release) self.app.exc_editor.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release) self.clicked_meas = 0 @@ -219,19 +216,17 @@ class Measurement(FlatCAMTool): if self.app.call_source == 'app': self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot) + self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) elif self.app.call_source == 'geo_editor': self.app.geo_editor.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move) self.app.geo_editor.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click) self.app.geo_editor.canvas.vis_disconnect('key_press', self.app.geo_editor.on_canvas_key) - self.app.geo_editor.canvas.vis_disconnect('key_release', self.app.geo_editor.on_canvas_key_release) self.app.geo_editor.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_canvas_click_release) elif self.app.call_source == 'exc_editor': self.app.exc_editor.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move) self.app.exc_editor.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click) self.app.exc_editor.canvas.vis_disconnect('key_press', self.app.exc_editor.on_canvas_key) - self.app.exc_editor.canvas.vis_disconnect('key_release', self.app.exc_editor.on_canvas_key_release) self.app.exc_editor.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_canvas_click_release) # we can safely connect the app mouse events to the measurement tool diff --git a/flatcamTools/ToolMove.py b/flatcamTools/ToolMove.py index 971d901f..562aff80 100644 --- a/flatcamTools/ToolMove.py +++ b/flatcamTools/ToolMove.py @@ -47,7 +47,7 @@ class ToolMove(FlatCAMTool): self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move) self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click) self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press) - self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot) + self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent) self.clicked_move = 0 From 05ae92726c3cf87c3575fa7fef7c666c173a9e51 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 16:59:17 +0200 Subject: [PATCH 07/27] - renamed the theme to layout because it is really a layout change - combined the geocutout and cutout_any TCL commands - work in progress --- FlatCAMApp.py | 16 +-- FlatCAMEditor.py | 16 +-- FlatCAMGUI.py | 39 ++++--- README.md | 2 + tclCommands/TclCommandCutoutAny.py | 179 ----------------------------- tclCommands/TclCommandGeoCutout.py | 178 +++++++++++++++++++--------- tclCommands/__init__.py | 1 - 7 files changed, 159 insertions(+), 272 deletions(-) delete mode 100644 tclCommands/TclCommandCutoutAny.py diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 408b23f6..7cdd099f 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1212,7 +1212,7 @@ class App(QtCore.QObject): self.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified) self.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace) - self.general_defaults_form.general_gui_group.theme_combo.activated.connect(self.on_theme) + self.general_defaults_form.general_gui_group.layout_combo.activated.connect(self.on_layout) # Modify G-CODE Plot Area TAB self.ui.code_editor.textChanged.connect(self.handleTextChanged) @@ -1257,7 +1257,7 @@ class App(QtCore.QObject): # Auto-complete KEYWORDS self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle', 'aligndrill', 'clear', - 'aligndrillgrid', 'cncjob', 'cutout', 'cutout_any', 'delete', 'drillcncjob', + 'aligndrillgrid', 'cncjob', 'cutout', 'delete', 'drillcncjob', 'export_gcode', 'export_svg', 'ext', 'exteriors', 'follow', 'geo_union', 'geocutout', 'get_names', 'get_sys', 'getsys', 'help', 'import_svg', 'interiors', 'isolate', 'join_excellon', @@ -3245,13 +3245,13 @@ class App(QtCore.QObject): self.general_defaults_form.general_gui_group.workspace_cb.setChecked(True) self.on_workspace() - def on_theme(self): - self.report_usage("on_theme()") + def on_layout(self): + self.report_usage("on_layout()") - current_theme= self.general_defaults_form.general_gui_group.theme_combo.get_value().lower() + current_layout= self.general_defaults_form.general_gui_group.layout_combo.get_value().lower() settings = QSettings("Open Source", "FlatCAM") - settings.setValue('theme', current_theme) + settings.setValue('layout', current_layout) # This will write the setting to the platform specific storage. del settings @@ -3265,7 +3265,7 @@ class App(QtCore.QObject): self.ui.removeToolBar(self.ui.geo_edit_toolbar) self.ui.removeToolBar(self.ui.snap_toolbar) - if current_theme == 'standard': + if current_layout == 'standard': ### TOOLBAR INSTALLATION ### self.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar') self.ui.toolbarfile.setObjectName('File_TB') @@ -3300,7 +3300,7 @@ class App(QtCore.QObject): self.ui.corner_snap_btn.setVisible(False) self.ui.snap_magnet.setVisible(False) - elif current_theme == 'compact': + elif current_layout == 'compact': ### TOOLBAR INSTALLATION ### self.ui.toolbarfile = QtWidgets.QToolBar('File Toolbar') self.ui.toolbarfile.setObjectName('File_TB') diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index 84cb50d7..a8fe3591 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -2083,16 +2083,16 @@ class FlatCAMGeoEditor(QtCore.QObject): self.app.ui.geo_edit_toolbar.setDisabled(True) settings = QSettings("Open Source", "FlatCAM") - if settings.contains("theme"): - theme = settings.value('theme', type=str) - if theme == 'standard': + if settings.contains("layout"): + layout = settings.value('layout', type=str) + if layout == 'standard': # self.app.ui.geo_edit_toolbar.setVisible(False) self.app.ui.snap_max_dist_entry.setEnabled(False) self.app.ui.corner_snap_btn.setEnabled(False) self.app.ui.snap_magnet.setVisible(False) self.app.ui.corner_snap_btn.setVisible(False) - elif theme == 'compact': + elif layout == 'compact': # self.app.ui.geo_edit_toolbar.setVisible(True) self.app.ui.snap_max_dist_entry.setEnabled(False) @@ -4224,16 +4224,16 @@ class FlatCAMExcEditor(QtCore.QObject): self.app.ui.exc_edit_toolbar.setDisabled(True) settings = QSettings("Open Source", "FlatCAM") - if settings.contains("theme"): - theme = settings.value('theme', type=str) - if theme == 'standard': + if settings.contains("layout"): + layout = settings.value('layout', type=str) + if layout == 'standard': # self.app.ui.exc_edit_toolbar.setVisible(False) self.app.ui.snap_max_dist_entry.setEnabled(False) self.app.ui.corner_snap_btn.setEnabled(False) self.app.ui.snap_magnet.setVisible(False) self.app.ui.corner_snap_btn.setVisible(False) - elif theme == 'compact': + elif layout == 'compact': # self.app.ui.exc_edit_toolbar.setVisible(True) self.app.ui.snap_max_dist_entry.setEnabled(False) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index beb885ea..e1e30c9e 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -435,11 +435,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.snap_toolbar.setObjectName('Snap_TB') settings = QSettings("Open Source", "FlatCAM") - if settings.contains("theme"): - theme = settings.value('theme', type=str) - if theme == 'standard': + if settings.contains("layout"): + layout = settings.value('layout', type=str) + if layout == 'standard': self.addToolBar(self.snap_toolbar) - elif theme == 'compact': + elif layout == 'compact': self.snap_toolbar.setMaximumHeight(30) self.splitter_left.addWidget(self.snap_toolbar) else: @@ -1336,9 +1336,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.restoreState(saved_gui_state) log.debug("FlatCAMGUI.__init__() --> UI state restored.") - if settings.contains("theme"): - theme = settings.value('theme', type=str) - if theme == 'standard': + if settings.contains("layout"): + layout = settings.value('layout', type=str) + if layout == 'standard': self.exc_edit_toolbar.setVisible(False) self.exc_edit_toolbar.setDisabled(True) self.geo_edit_toolbar.setVisible(False) @@ -1346,7 +1346,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.corner_snap_btn.setVisible(False) self.snap_magnet.setVisible(False) - elif theme == 'compact': + elif layout == 'compact': self.exc_edit_toolbar.setDisabled(True) self.geo_edit_toolbar.setDisabled(True) self.snap_magnet.setVisible(True) @@ -1477,9 +1477,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.grid_snap_btn.trigger() settings = QSettings("Open Source", "FlatCAM") - if settings.contains("theme"): - theme = settings.value('theme', type=str) - if theme == 'standard': + if settings.contains("layout"): + layout = settings.value('layout', type=str) + if layout == 'standard': self.exc_edit_toolbar.setVisible(False) self.exc_edit_toolbar.setDisabled(True) self.geo_edit_toolbar.setVisible(False) @@ -1487,7 +1487,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.corner_snap_btn.setVisible(False) self.snap_magnet.setVisible(False) - elif theme == 'compact': + elif layout == 'compact': self.exc_edit_toolbar.setVisible(True) self.exc_edit_toolbar.setDisabled(True) self.geo_edit_toolbar.setVisible(True) @@ -2219,14 +2219,15 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): self.form_box_child_11.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) # Theme selection - self.theme_label = QtWidgets.QLabel('Theme:') + self.layout_label = QtWidgets.QLabel('Layout:') self.alt_sf_color_label.setToolTip( - "Select a theme for FlatCAM." + "Select an layout for FlatCAM." ) - self.theme_combo = FCComboBox() - self.theme_combo.addItem("Standard") - self.theme_combo.addItem("Compact") - self.theme_combo.setCurrentIndex(0) + self.layout_combo = FCComboBox() + self.layout_combo.addItem("Choose ...") + self.layout_combo.addItem("Standard") + self.layout_combo.addItem("Compact") + self.layout_combo.setCurrentIndex(0) # Just to add empty rows self.spacelabel = QtWidgets.QLabel('') @@ -2255,7 +2256,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): self.form_box.addRow(self.sel_draw_color_label, self.form_box_child_11) self.form_box.addRow(self.spacelabel, self.spacelabel) - self.form_box.addRow(self.theme_label, self.theme_combo) + self.form_box.addRow(self.layout_label, self.layout_combo) # Add the QFormLayout that holds the Application general defaults # to the main layout of this TAB self.layout.addLayout(self.form_box) diff --git a/README.md b/README.md index 7941afd3..aba4d043 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ CAD program, and create G-Code for Isolation routing. - fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots. - changed the messages for Units Conversion - all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent() +- renamed the theme to layout because it is really a layout change +- combined the geocutout and cutout_any TCL commands - work in progress 5.02.3019 diff --git a/tclCommands/TclCommandCutoutAny.py b/tclCommands/TclCommandCutoutAny.py deleted file mode 100644 index 49fd718c..00000000 --- a/tclCommands/TclCommandCutoutAny.py +++ /dev/null @@ -1,179 +0,0 @@ -from ObjectCollection import * -from tclCommands.TclCommand import TclCommand - - -class TclCommandCutoutAny(TclCommand): - """ - Tcl shell command to create a board cutout geometry. Allow cutout for any shape. - - example: - - """ - - # List of all command aliases, to be able use old - # names for backward compatibility (add_poly, add_polygon) - aliases = ['cutout_any', 'cut_any'] - - # Dictionary of types from Tcl command, needs to be ordered - arg_names = collections.OrderedDict([ - ('name', str), - ]) - - # Dictionary of types from Tcl command, needs to be ordered, - # this is for options like -optionname value - option_types = collections.OrderedDict([ - ('dia', float), - ('margin', float), - ('gapsize', float), - ('gaps', str) - ]) - - # array of mandatory options for current Tcl command: required = {'name','outname'} - required = ['name'] - - # structured help for current command, args needs to be ordered - help = { - 'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape', - 'args': collections.OrderedDict([ - ('name', 'Name of the object.'), - ('dia', 'Tool diameter.'), - ('margin', 'Margin over bounds.'), - ('gapsize', 'size of gap.'), - ('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, " - "'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts") - ]), - 'examples': [] - } - - def execute(self, args, unnamed_args): - """ - - :param args: - :param unnamed_args: - :return: - """ - - def subtract_rectangle(obj_, x0, y0, x1, y1): - pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] - obj_.subtract_polygon(pts) - - if 'name' in args: - name = args['name'] - else: - self.app.inform.emit( - "[WARNING]The name of the object for which cutout is done is missing. Add it and retry.") - return - - if 'margin' in args: - margin = args['margin'] - else: - margin = 0.001 - - if 'dia' in args: - dia = args['dia'] - else: - dia = 0.1 - - if 'gaps' in args: - gaps = args['gaps'] - else: - gaps = 4 - - if 'gapsize' in args: - gapsize = args['gapsize'] - else: - gapsize = 0.1 - - # Get source object. - try: - cutout_obj = self.app.collection.get_by_name(str(name)) - except: - return "Could not retrieve object: %s" % name - - if 0 in {dia}: - self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.") - return "Tool Diameter is zero value. Change it to a positive integer." - - if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]: - self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. " - "Fill in a correct value and retry. ") - return - - # Get min and max data for each object as we just cut rectangles across X or Y - xmin, ymin, xmax, ymax = cutout_obj.bounds() - px = 0.5 * (xmin + xmax) + margin - py = 0.5 * (ymin + ymax) + margin - lenghtx = (xmax - xmin) + (margin * 2) - lenghty = (ymax - ymin) + (margin * 2) - - gapsize = gapsize + (dia / 2) - - if isinstance(cutout_obj, FlatCAMGeometry): - # rename the obj name so it can be identified as cutout - cutout_obj.options["name"] += "_cutout" - elif isinstance(cutout_obj, FlatCAMGerber): - cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp") - ext_obj = self.app.collection.get_by_name("_temp") - - def geo_init(geo_obj, app_obj): - geo_obj.solid_geometry = obj_exteriors - - outname = cutout_obj.options["name"] + "_cutout" - - obj_exteriors = ext_obj.get_exteriors() - self.app.new_object('geometry', outname, geo_init) - - self.app.collection.set_all_inactive() - self.app.collection.set_active("_temp") - self.app.on_delete() - - cutout_obj = self.app.collection.get_by_name(outname) - else: - self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.") - return - - try: - gaps_u = int(gaps) - except ValueError: - gaps_u = gaps - - if gaps_u == 8 or gaps_u == '2lr': - subtract_rectangle(cutout_obj, - xmin - gapsize, # botleft_x - py - gapsize + lenghty / 4, # botleft_y - xmax + gapsize, # topright_x - py + gapsize + lenghty / 4) # topright_y - subtract_rectangle(cutout_obj, - xmin - gapsize, - py - gapsize - lenghty / 4, - xmax + gapsize, - py + gapsize - lenghty / 4) - - if gaps_u == 8 or gaps_u == '2tb': - subtract_rectangle(cutout_obj, - px - gapsize + lenghtx / 4, - ymin - gapsize, - px + gapsize + lenghtx / 4, - ymax + gapsize) - subtract_rectangle(cutout_obj, - px - gapsize - lenghtx / 4, - ymin - gapsize, - px + gapsize - lenghtx / 4, - ymax + gapsize) - - if gaps_u == 4 or gaps_u == 'lr': - subtract_rectangle(cutout_obj, - xmin - gapsize, - py - gapsize, - xmax + gapsize, - py + gapsize) - - if gaps_u == 4 or gaps_u == 'tb': - subtract_rectangle(cutout_obj, - px - gapsize, - ymin - gapsize, - px + gapsize, - ymax + gapsize) - - cutout_obj.plot() - self.app.inform.emit("[success]Any-form Cutout operation finished.") diff --git a/tclCommands/TclCommandGeoCutout.py b/tclCommands/TclCommandGeoCutout.py index af12b54d..7067adc4 100644 --- a/tclCommands/TclCommandGeoCutout.py +++ b/tclCommands/TclCommandGeoCutout.py @@ -4,20 +4,23 @@ from tclCommands.TclCommand import TclCommandSignaled class TclCommandGeoCutout(TclCommandSignaled): """ - Tcl shell command to cut holding gaps from geometry. - """ + Tcl shell command to create a board cutout geometry. Allow cutout for any shape. Cuts holding gaps from geometry. - # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) - aliases = ['geocutout'] + example: - # Dictionary of types from Tcl command, needs to be ordered. - # For positional arguments + """ + + # List of all command aliases, to be able use old + # names for backward compatibility (add_poly, add_polygon) + aliases = ['geocutout', 'geoc'] + + # Dictionary of types from Tcl command, needs to be ordered arg_names = collections.OrderedDict([ - ('name', str) + ('name', str), ]) - # Dictionary of types from Tcl command, needs to be ordered. - # For options like -optionname value + # Dictionary of types from Tcl command, needs to be ordered, + # this is for options like -optionname value option_types = collections.OrderedDict([ ('dia', float), ('margin', float), @@ -30,99 +33,160 @@ class TclCommandGeoCutout(TclCommandSignaled): # structured help for current command, args needs to be ordered help = { - 'main': "Cut holding gaps from geometry.", + 'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape', 'args': collections.OrderedDict([ - ('name', 'Name of the geometry object.'), + ('name', 'Name of the object.'), ('dia', 'Tool diameter.'), ('margin', 'Margin over bounds.'), - ('gapsize', 'Size of gap.'), - ('gaps', 'Type of gaps.'), + ('gapsize', 'size of gap.'), + ('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, " + "'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts") ]), 'examples': [" #isolate margin for example from fritzing arduino shield or any svg etc\n" + - " isolate BCu_margin -dia 3 -overlap 1\n" + - "\n" + - " #create exteriors from isolated object\n" + - " exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" + - "\n" + - " #delete isolated object if you dond need id anymore\n" + - " delete BCu_margin_iso\n" + - "\n" + - " #finally cut holding gaps\n" + - " geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"] + " isolate BCu_margin -dia 3 -overlap 1\n" + + "\n" + + " #create exteriors from isolated object\n" + + " exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" + + "\n" + + " #delete isolated object if you dond need id anymore\n" + + " delete BCu_margin_iso\n" + + "\n" + + " #finally cut holding gaps\n" + + " geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"] } def execute(self, args, unnamed_args): """ - execute current TCL shell command - :param args: array of known named arguments and options - :param unnamed_args: array of other values which were passed into command - without -somename and we do not have them in known arg_names - :return: None or exception + :param args: + :param unnamed_args: + :return: """ - # How gaps wil be rendered: - # lr - left + right - # tb - top + bottom - # 4 - left + right +top + bottom - # 2lr - 2*left + 2*right - # 2tb - 2*top + 2*bottom - # 8 - 2*left + 2*right +2*top + 2*bottom - - name = args['name'] - obj = None - def subtract_rectangle(obj_, x0, y0, x1, y1): pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] obj_.subtract_polygon(pts) + if 'name' in args: + name = args['name'] + else: + self.app.inform.emit( + "[WARNING]The name of the object for which cutout is done is missing. Add it and retry.") + return + + if 'margin' in args: + margin = args['margin'] + else: + margin = 0.001 + + if 'dia' in args: + dia = args['dia'] + else: + dia = 0.1 + + if 'gaps' in args: + gaps = args['gaps'] + else: + gaps = 4 + + if 'gapsize' in args: + gapsize = args['gapsize'] + else: + gapsize = 0.1 + + # Get source object. try: - obj = self.app.collection.get_by_name(str(name)) + cutout_obj = self.app.collection.get_by_name(str(name)) except: - self.raise_tcl_error("Could not retrieve object: %s" % name) + return "Could not retrieve object: %s" % name + + if 0 in {dia}: + self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.") + return "Tool Diameter is zero value. Change it to a positive integer." + + if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]: + self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. " + "Fill in a correct value and retry. ") + return # Get min and max data for each object as we just cut rectangles across X or Y - xmin, ymin, xmax, ymax = obj.bounds() - px = 0.5 * (xmin + xmax) - py = 0.5 * (ymin + ymax) - lenghtx = (xmax - xmin) - lenghty = (ymax - ymin) - gapsize = args['gapsize'] + (args['dia'] / 2) + xmin, ymin, xmax, ymax = cutout_obj.bounds() + px = 0.5 * (xmin + xmax) + margin + py = 0.5 * (ymin + ymax) + margin + lenghtx = (xmax - xmin) + (margin * 2) + lenghty = (ymax - ymin) + (margin * 2) - if args['gaps'] == '8' or args['gaps'] == '2lr': - subtract_rectangle(obj, + gapsize = gapsize + (dia / 2) + + if isinstance(cutout_obj, FlatCAMGeometry): + # rename the obj name so it can be identified as cutout + cutout_obj.options["name"] += "_cutout" + elif isinstance(cutout_obj, FlatCAMGerber): + + def geo_init(geo_obj, app_obj): + geo_obj.solid_geometry = obj_exteriors + + outname = cutout_obj.options["name"] + "_cutout" + cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp") + ext_obj = self.app.collection.get_by_name("_temp") + + try: + obj_exteriors = ext_obj.get_exteriors() + except: + obj_exteriors = ext_obj.solid_geometry + + self.app.new_object('geometry', outname, geo_init) + self.app.collection.set_all_inactive() + self.app.collection.set_active("_temp") + self.app.on_delete() + + cutout_obj = self.app.collection.get_by_name(outname) + else: + self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.") + return + + try: + gaps_u = int(gaps) + except ValueError: + gaps_u = gaps + + if gaps_u == 8 or gaps_u == '2lr': + subtract_rectangle(cutout_obj, xmin - gapsize, # botleft_x py - gapsize + lenghty / 4, # botleft_y xmax + gapsize, # topright_x py + gapsize + lenghty / 4) # topright_y - subtract_rectangle(obj, + subtract_rectangle(cutout_obj, xmin - gapsize, py - gapsize - lenghty / 4, xmax + gapsize, py + gapsize - lenghty / 4) - if args['gaps'] == '8' or args['gaps'] == '2tb': - subtract_rectangle(obj, + if gaps_u == 8 or gaps_u == '2tb': + subtract_rectangle(cutout_obj, px - gapsize + lenghtx / 4, ymin - gapsize, px + gapsize + lenghtx / 4, ymax + gapsize) - subtract_rectangle(obj, + subtract_rectangle(cutout_obj, px - gapsize - lenghtx / 4, ymin - gapsize, px + gapsize - lenghtx / 4, ymax + gapsize) - if args['gaps'] == '4' or args['gaps'] == 'lr': - subtract_rectangle(obj, + if gaps_u == 4 or gaps_u == 'lr': + subtract_rectangle(cutout_obj, xmin - gapsize, py - gapsize, xmax + gapsize, py + gapsize) - if args['gaps'] == '4' or args['gaps'] == 'tb': - subtract_rectangle(obj, + if gaps_u == 4 or gaps_u == 'tb': + subtract_rectangle(cutout_obj, px - gapsize, ymin - gapsize, px + gapsize, ymax + gapsize) + + cutout_obj.plot() + self.app.inform.emit("[success]Any-form Cutout operation finished.") diff --git a/tclCommands/__init__.py b/tclCommands/__init__.py index 911a3203..b06014d7 100644 --- a/tclCommands/__init__.py +++ b/tclCommands/__init__.py @@ -12,7 +12,6 @@ import tclCommands.TclCommandAlignDrillGrid import tclCommands.TclCommandClearShell import tclCommands.TclCommandCncjob import tclCommands.TclCommandCutout -import tclCommands.TclCommandCutoutAny import tclCommands.TclCommandDelete import tclCommands.TclCommandDrillcncjob import tclCommands.TclCommandExportGcode From 9dfbae7515d0beec3d388e6ea088fdbcc3ff3800 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 20:29:53 +0200 Subject: [PATCH 08/27] - added plot kind for CNC Job in the App Preferences --- FlatCAM.py | 4 +++- FlatCAMApp.py | 7 ++++++- FlatCAMGUI.py | 47 ++++++++++++++++++++++++++++++++++------------- FlatCAMObj.py | 2 +- ObjectUI.py | 9 +++++++-- README.md | 1 + 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/FlatCAM.py b/FlatCAM.py index 440181f0..28eb08bf 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -5,7 +5,7 @@ from PyQt5 import QtGui, QtCore, QtWidgets from FlatCAMApp import App from multiprocessing import freeze_support import VisPyPatches - +import qtmodern.styles, qtmodern.windows if sys.platform == "win32": # cx_freeze 'module win32' workaround @@ -34,5 +34,7 @@ if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) fc = App() + + # qtmodern.styles.dark(app) sys.exit(app.exec_()) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 7cdd099f..de12606f 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -394,6 +394,7 @@ class App(QtCore.QObject): "geometry_extracut": self.geometry_defaults_form.geometry_opt_group.extracut_cb, "cncjob_plot": self.cncjob_defaults_form.cncjob_gen_group.plot_cb, + "cncjob_plot_kind": self.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio, "cncjob_tooldia": self.cncjob_defaults_form.cncjob_gen_group.tooldia_entry, "cncjob_coords_decimals": self.cncjob_defaults_form.cncjob_gen_group.coords_dec_entry, "cncjob_fr_decimals": self.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry, @@ -580,6 +581,7 @@ class App(QtCore.QObject): "geometry_circle_steps": 64, "cncjob_plot": True, + "cncjob_plot_kind": 'all', "cncjob_tooldia": 0.0393701, "cncjob_coords_decimals": 4, "cncjob_fr_decimals": 2, @@ -2288,7 +2290,10 @@ class App(QtCore.QObject): def worker_task(obj): with self.proc_container.new("Plotting"): - obj.plot() + if isinstance(obj, FlatCAMCNCjob): + obj.plot(kind=self.defaults["cncjob_plot_kind"]) + else: + obj.plot() t1 = time.time() # DEBUG self.log.debug("%f seconds adding object and plotting." % (t1 - t0)) self.object_plotted.emit(obj) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index e1e30c9e..efdb7cc6 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1968,7 +1968,7 @@ class CNCJobPreferencesUI(QtWidgets.QWidget): self.setLayout(self.layout) self.cncjob_gen_group = CNCJobGenPrefGroupUI() - self.cncjob_gen_group.setFixedWidth(260) + self.cncjob_gen_group.setFixedWidth(270) self.cncjob_opt_group = CNCJobOptPrefGroupUI() self.cncjob_opt_group.setFixedWidth(260) @@ -3333,24 +3333,45 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI): grid0 = QtWidgets.QGridLayout() self.layout.addLayout(grid0) + grid0.setColumnStretch(1, 1) + grid0.setColumnStretch(2, 1) # Plot CB # self.plot_cb = QtWidgets.QCheckBox('Plot') - self.plot_cb = FCCheckBox('Plot') + self.plot_cb = FCCheckBox('Plot Object') self.plot_cb.setToolTip( "Plot (show) this object." ) grid0.addWidget(self.plot_cb, 0, 0) + # Plot Kind + self.cncplot_method_label = QtWidgets.QLabel("Plot kind:") + self.cncplot_method_label.setToolTip( + "This selects the kind of geometries on the canvas to plot.\n" + "Those can be either of type 'Travel' which means the moves\n" + "above the work piece or it can be of type 'Cut',\n" + "which means the moves that cut into the material." + ) + + self.cncplot_method_radio = RadioSet([ + {"label": "All", "value": "all"}, + {"label": "Travel", "value": "travel"}, + {"label": "Cut", "value": "cut"} + ], stretch=False) + + grid0.addWidget(self.cncplot_method_label, 1, 0) + grid0.addWidget(self.cncplot_method_radio, 1, 1) + grid0.addWidget(QtWidgets.QLabel(''), 1, 2) + # Number of circle steps for circular aperture linear approximation self.steps_per_circle_label = QtWidgets.QLabel("Circle Steps:") self.steps_per_circle_label.setToolTip( "The number of circle steps for GCode \n" "circle and arc shapes linear approximation." ) - grid0.addWidget(self.steps_per_circle_label, 1, 0) + grid0.addWidget(self.steps_per_circle_label, 2, 0) self.steps_per_circle_entry = IntEntry() - grid0.addWidget(self.steps_per_circle_entry, 1, 1) + grid0.addWidget(self.steps_per_circle_entry, 2, 1) # Tool dia for plot tdlabel = QtWidgets.QLabel('Tool dia:') @@ -3358,29 +3379,29 @@ class CNCJobGenPrefGroupUI(OptionsGroupUI): "Diameter of the tool to be\n" "rendered in the plot." ) - grid0.addWidget(tdlabel, 2, 0) + grid0.addWidget(tdlabel, 3, 0) self.tooldia_entry = LengthEntry() - grid0.addWidget(self.tooldia_entry, 2, 1) + grid0.addWidget(self.tooldia_entry, 3, 1) # Number of decimals to use in GCODE coordinates - cdeclabel = QtWidgets.QLabel('Coords decimals:') + cdeclabel = QtWidgets.QLabel('Coords dec.:') cdeclabel.setToolTip( "The number of decimals to be used for \n" "the X, Y, Z coordinates in CNC code (GCODE, etc.)" ) - grid0.addWidget(cdeclabel, 3, 0) + grid0.addWidget(cdeclabel, 4, 0) self.coords_dec_entry = IntEntry() - grid0.addWidget(self.coords_dec_entry, 3, 1) + grid0.addWidget(self.coords_dec_entry, 4, 1) # Number of decimals to use in GCODE feedrate - frdeclabel = QtWidgets.QLabel('Feedrate decimals:') + frdeclabel = QtWidgets.QLabel('Feedrate dec.:') frdeclabel.setToolTip( "The number of decimals to be used for \n" - "the feedrate in CNC code (GCODE, etc.)" + "the Feedrate parameter in CNC code (GCODE, etc.)" ) - grid0.addWidget(frdeclabel, 4, 0) + grid0.addWidget(frdeclabel, 5, 0) self.fr_dec_entry = IntEntry() - grid0.addWidget(self.fr_dec_entry, 4, 1) + grid0.addWidget(self.fr_dec_entry, 5, 1) self.layout.addStretch() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 645c4573..00875ec8 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -4235,7 +4235,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): self.to_form() # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob - self.ui.cncplot_method_combo.set_value('all') + self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"]) self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click) self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click) diff --git a/ObjectUI.py b/ObjectUI.py index fc8a8287..35f29727 100644 --- a/ObjectUI.py +++ b/ObjectUI.py @@ -1129,9 +1129,14 @@ class CNCObjectUI(ObjectUI): {"label": "Cut", "value": "cut"} ], stretch=False) - f_lay = QtWidgets.QFormLayout() + f_lay = QtWidgets.QGridLayout() + f_lay.setColumnStretch(1, 1) + f_lay.setColumnStretch(2, 1) + self.custom_box.addLayout(f_lay) - f_lay.addRow(self.cncplot_method_label, self.cncplot_method_combo) + f_lay.addWidget(self.cncplot_method_label, 0, 0) + f_lay.addWidget(self.cncplot_method_combo, 0, 1) + f_lay.addWidget(QtWidgets.QLabel(''), 0, 2) e1_lbl = QtWidgets.QLabel('') self.custom_box.addWidget(e1_lbl) diff --git a/README.md b/README.md index aba4d043..172ea83a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ CAD program, and create G-Code for Isolation routing. - changed the messages for Units Conversion - all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent() - renamed the theme to layout because it is really a layout change +- added plot kind for CNC Job in the App Preferences - combined the geocutout and cutout_any TCL commands - work in progress 5.02.3019 From 2ee80990e52db772b89058b9fc4d966da4e23bd7 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 21:37:50 +0200 Subject: [PATCH 09/27] - added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) - fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences --- FlatCAMApp.py | 4 ++++ FlatCAMGUI.py | 25 +++++++++++++++++-------- FlatCAMObj.py | 3 ++- README.md | 2 ++ postprocessors/default.py | 2 +- tclCommands/TclCommandGeoCutout.py | 19 ++++++------------- 6 files changed, 32 insertions(+), 23 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index de12606f..9f109c2e 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -3229,6 +3229,10 @@ class App(QtCore.QObject): self.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value(new_val_sel) self.defaults['global_sel_draw_color'] = new_val_sel + def on_deselect_all(self): + self.collection.set_all_inactive() + self.delete_selection_shape() + def on_workspace_modified(self): self.save_defaults(silent=True) self.plotcanvas.draw_workspace() diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index efdb7cc6..ec7586d1 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -988,6 +988,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): SPACE  En(Dis)able Obj Plot + + Escape +  Deselects all objects + @@ -1539,7 +1543,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.on_file_saveproject() # Toggle Plot Area - if key == QtCore.Qt.Key_F10: + if key == QtCore.Qt.Key_F10 or key == 'F10': self.app.on_toggle_plotarea() return @@ -1647,16 +1651,16 @@ class FlatCAMGUI(QtWidgets.QMainWindow): return # Toggle Fullscreen - if key == QtCore.Qt.Key_F10: + if key == QtCore.Qt.Key_F10 or key == 'F10': self.app.on_fullscreen() return else: # Open Manual - if key == QtCore.Qt.Key_F1: + if key == QtCore.Qt.Key_F1 or key == 'F1': webbrowser.open(self.app.manual_url) # Open Video Help - if key == QtCore.Qt.Key_F2: + if key == QtCore.Qt.Key_F2 or key == 'F2': webbrowser.open(self.app.video_url) # Switch to Project Tab @@ -1672,10 +1676,15 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.on_select_tab('tool') # Delete - if key == QtCore.Qt.Key_Delete and active: - # Delete via the application to - # ensure cleanup of the GUI - active.app.on_delete() + if key == QtCore.Qt.Key_Delete or key == 'Delete': + if active: + # Delete via the application to + # ensure cleanup of the GUI + active.app.on_delete() + + # Escape = Deselect All + if key == QtCore.Qt.Key_Escape or key == 'Escape': + self.app.on_deselect_all() # Space = Toggle Active/Inactive if key == QtCore.Qt.Key_Space: diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 00875ec8..377e7f5b 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1701,7 +1701,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): job_obj.dwell = self.options["dwell"] job_obj.dwelltime = self.options["dwelltime"] job_obj.pp_excellon_name = pp_excellon_name - job_obj.toolchange_xy = self.app.defaults["excellon_toolchangexy"] + job_obj.toolchange_xy_type = "excellon" job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"]) job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"]) @@ -1740,6 +1740,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, drillz=self.options['drillz'], toolchange=self.options["toolchange"], + toolchangexy=self.app.defaults["excellon_toolchangexy"], toolchangez=self.options["toolchangez"], startz=self.options["startz"], endz=self.options["endz"], diff --git a/README.md b/README.md index 172ea83a..967ce074 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ CAD program, and create G-Code for Isolation routing. - renamed the theme to layout because it is really a layout change - added plot kind for CNC Job in the App Preferences - combined the geocutout and cutout_any TCL commands - work in progress +- added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) +- fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences 5.02.3019 diff --git a/postprocessors/default.py b/postprocessors/default.py index e7d1d8e1..7441bc44 100644 --- a/postprocessors/default.py +++ b/postprocessors/default.py @@ -45,7 +45,7 @@ class default(FlatCAMPostProc): gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': - gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n' else: gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n' diff --git a/tclCommands/TclCommandGeoCutout.py b/tclCommands/TclCommandGeoCutout.py index 7067adc4..84ac3d23 100644 --- a/tclCommands/TclCommandGeoCutout.py +++ b/tclCommands/TclCommandGeoCutout.py @@ -1,6 +1,6 @@ from ObjectCollection import * from tclCommands.TclCommand import TclCommandSignaled - +from copy import deepcopy class TclCommandGeoCutout(TclCommandSignaled): """ @@ -124,21 +124,14 @@ class TclCommandGeoCutout(TclCommandSignaled): elif isinstance(cutout_obj, FlatCAMGerber): def geo_init(geo_obj, app_obj): - geo_obj.solid_geometry = obj_exteriors + try: + geo_obj.solid_geometry = cutout_obj.isolation_geometry((dia / 2), iso_type=0) + except Exception as e: + log.debug("TclCommandGeoCutout.execute() --> %s" % str(e)) + return 'fail' outname = cutout_obj.options["name"] + "_cutout" - cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp") - ext_obj = self.app.collection.get_by_name("_temp") - - try: - obj_exteriors = ext_obj.get_exteriors() - except: - obj_exteriors = ext_obj.solid_geometry - self.app.new_object('geometry', outname, geo_init) - self.app.collection.set_all_inactive() - self.app.collection.set_active("_temp") - self.app.on_delete() cutout_obj = self.app.collection.get_by_name(outname) else: From 1a8db0b5d927445cd3bdbb68403539373f4b2c40 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 6 Feb 2019 22:32:32 +0200 Subject: [PATCH 10/27] - fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects) --- FlatCAMEditor.py | 6 +- README.md | 1 + camlib.py | 9 +- tclCommands/TclCommandGeoCutout.py | 185 ++++++++++++++++++++++------- 4 files changed, 152 insertions(+), 49 deletions(-) diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index a8fe3591..86559660 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -106,21 +106,21 @@ class BufferSelectionTool(FlatCAMTool): def on_buffer(self): buffer_distance = self.buffer_distance_entry.get_value() # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) join_style = self.buffer_corner_cb.currentIndex() + 1 self.draw_app.buffer(buffer_distance, join_style) def on_buffer_int(self): buffer_distance = self.buffer_distance_entry.get_value() # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) join_style = self.buffer_corner_cb.currentIndex() + 1 self.draw_app.buffer_int(buffer_distance, join_style) def on_buffer_ext(self): buffer_distance = self.buffer_distance_entry.get_value() # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) join_style = self.buffer_corner_cb.currentIndex() + 1 self.draw_app.buffer_ext(buffer_distance, join_style) diff --git a/README.md b/README.md index 967ce074..17c50f26 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ CAD program, and create G-Code for Isolation routing. - combined the geocutout and cutout_any TCL commands - work in progress - added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es) - fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences +- fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects) 5.02.3019 diff --git a/camlib.py b/camlib.py index b85eb553..3e89a412 100644 --- a/camlib.py +++ b/camlib.py @@ -494,7 +494,7 @@ class Geometry(object): # # return self.flat_geometry, self.flat_geometry_rtree - def isolation_geometry(self, offset, iso_type=2): + def isolation_geometry(self, offset, iso_type=2, corner=None): """ Creates contours around geometry at a given offset distance. @@ -503,6 +503,7 @@ class Geometry(object): :type offset: float :param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete) :type integer + :param corner: type of corner for the isolation: 0 = round; 1 = square; 2= beveled (line that connects the ends) :return: The buffered geometry. :rtype: Shapely.MultiPolygon or Shapely.Polygon """ @@ -537,7 +538,11 @@ class Geometry(object): if offset == 0: geo_iso = self.solid_geometry else: - geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)) + if corner is None: + geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4)) + else: + geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner) + # end of replaced block if iso_type == 2: diff --git a/tclCommands/TclCommandGeoCutout.py b/tclCommands/TclCommandGeoCutout.py index 84ac3d23..db49be02 100644 --- a/tclCommands/TclCommandGeoCutout.py +++ b/tclCommands/TclCommandGeoCutout.py @@ -55,6 +55,8 @@ class TclCommandGeoCutout(TclCommandSignaled): " geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"] } + flat_geometry = [] + def execute(self, args, unnamed_args): """ @@ -63,10 +65,62 @@ class TclCommandGeoCutout(TclCommandSignaled): :return: """ + def subtract_rectangle(obj_, x0, y0, x1, y1): pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] obj_.subtract_polygon(pts) + def substract_rectangle_geo(geo, x0, y0, x1, y1): + pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] + + + def flatten(geometry=None, reset=True, pathonly=False): + """ + Creates a list of non-iterable linear geometry objects. + Polygons are expanded into its exterior and interiors if specified. + + Results are placed in flat_geometry + + :param geometry: Shapely type or list or list of list of such. + :param reset: Clears the contents of self.flat_geometry. + :param pathonly: Expands polygons into linear elements. + """ + + if reset: + self.flat_geometry = [] + + ## If iterable, expand recursively. + try: + for geo in geometry: + if geo is not None: + flatten(geometry=geo, + reset=False, + pathonly=pathonly) + + ## Not iterable, do the actual indexing and add. + except TypeError: + if pathonly and type(geometry) == Polygon: + self.flat_geometry.append(geometry.exterior) + flatten(geometry=geometry.interiors, + reset=False, + pathonly=True) + else: + self.flat_geometry.append(geometry) + + return self.flat_geometry + + flat_geometry = flatten(geo, pathonly=True) + + polygon = Polygon(pts) + toolgeo = cascaded_union(polygon) + diffs = [] + for target in flat_geometry: + if type(target) == LineString or type(target) == LinearRing: + diffs.append(target.difference(toolgeo)) + else: + log.warning("Not implemented.") + return cascaded_union(diffs) + if 'name' in args: name = args['name'] else: @@ -116,20 +170,105 @@ class TclCommandGeoCutout(TclCommandSignaled): lenghtx = (xmax - xmin) + (margin * 2) lenghty = (ymax - ymin) + (margin * 2) - gapsize = gapsize + (dia / 2) + gapsize = gapsize / 2 + (dia / 2) + + try: + gaps_u = int(gaps) + except ValueError: + gaps_u = gaps if isinstance(cutout_obj, FlatCAMGeometry): # rename the obj name so it can be identified as cutout cutout_obj.options["name"] += "_cutout" + + if gaps_u == 8 or gaps_u == '2lr': + subtract_rectangle(cutout_obj, + xmin - gapsize, # botleft_x + py - gapsize + lenghty / 4, # botleft_y + xmax + gapsize, # topright_x + py + gapsize + lenghty / 4) # topright_y + subtract_rectangle(cutout_obj, + xmin - gapsize, + py - gapsize - lenghty / 4, + xmax + gapsize, + py + gapsize - lenghty / 4) + + if gaps_u == 8 or gaps_u == '2tb': + subtract_rectangle(cutout_obj, + px - gapsize + lenghtx / 4, + ymin - gapsize, + px + gapsize + lenghtx / 4, + ymax + gapsize) + subtract_rectangle(cutout_obj, + px - gapsize - lenghtx / 4, + ymin - gapsize, + px + gapsize - lenghtx / 4, + ymax + gapsize) + + if gaps_u == 4 or gaps_u == 'lr': + subtract_rectangle(cutout_obj, + xmin - gapsize, + py - gapsize, + xmax + gapsize, + py + gapsize) + + if gaps_u == 4 or gaps_u == 'tb': + subtract_rectangle(cutout_obj, + px - gapsize, + ymin - gapsize, + px + gapsize, + ymax + gapsize) + + cutout_obj.plot() + self.app.inform.emit("[success]Any-form Cutout operation finished.") elif isinstance(cutout_obj, FlatCAMGerber): def geo_init(geo_obj, app_obj): try: - geo_obj.solid_geometry = cutout_obj.isolation_geometry((dia / 2), iso_type=0) + geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2) except Exception as e: log.debug("TclCommandGeoCutout.execute() --> %s" % str(e)) return 'fail' + if gaps_u == 8 or gaps_u == '2lr': + geo = substract_rectangle_geo(geo, + xmin - gapsize, # botleft_x + py - gapsize + lenghty / 4, # botleft_y + xmax + gapsize, # topright_x + py + gapsize + lenghty / 4) # topright_y + geo = substract_rectangle_geo(geo, + xmin - gapsize, + py - gapsize - lenghty / 4, + xmax + gapsize, + py + gapsize - lenghty / 4) + + if gaps_u == 8 or gaps_u == '2tb': + geo = substract_rectangle_geo(geo, + px - gapsize + lenghtx / 4, + ymin - gapsize, + px + gapsize + lenghtx / 4, + ymax + gapsize) + geo = substract_rectangle_geo(geo, + px - gapsize - lenghtx / 4, + ymin - gapsize, + px + gapsize - lenghtx / 4, + ymax + gapsize) + + if gaps_u == 4 or gaps_u == 'lr': + geo = substract_rectangle_geo(geo, + xmin - gapsize, + py - gapsize, + xmax + gapsize, + py + gapsize) + + if gaps_u == 4 or gaps_u == 'tb': + geo = substract_rectangle_geo(geo, + px - gapsize, + ymin - gapsize, + px + gapsize, + ymax + gapsize) + geo_obj.solid_geometry = geo + outname = cutout_obj.options["name"] + "_cutout" self.app.new_object('geometry', outname, geo_init) @@ -138,48 +277,6 @@ class TclCommandGeoCutout(TclCommandSignaled): self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.") return - try: - gaps_u = int(gaps) - except ValueError: - gaps_u = gaps - if gaps_u == 8 or gaps_u == '2lr': - subtract_rectangle(cutout_obj, - xmin - gapsize, # botleft_x - py - gapsize + lenghty / 4, # botleft_y - xmax + gapsize, # topright_x - py + gapsize + lenghty / 4) # topright_y - subtract_rectangle(cutout_obj, - xmin - gapsize, - py - gapsize - lenghty / 4, - xmax + gapsize, - py + gapsize - lenghty / 4) - if gaps_u == 8 or gaps_u == '2tb': - subtract_rectangle(cutout_obj, - px - gapsize + lenghtx / 4, - ymin - gapsize, - px + gapsize + lenghtx / 4, - ymax + gapsize) - subtract_rectangle(cutout_obj, - px - gapsize - lenghtx / 4, - ymin - gapsize, - px + gapsize - lenghtx / 4, - ymax + gapsize) - if gaps_u == 4 or gaps_u == 'lr': - subtract_rectangle(cutout_obj, - xmin - gapsize, - py - gapsize, - xmax + gapsize, - py + gapsize) - - if gaps_u == 4 or gaps_u == 'tb': - subtract_rectangle(cutout_obj, - px - gapsize, - ymin - gapsize, - px + gapsize, - ymax + gapsize) - - cutout_obj.plot() - self.app.inform.emit("[success]Any-form Cutout operation finished.") From 700222d5dcfeda199d6b8442412484f99220eebc Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 00:53:51 +0200 Subject: [PATCH 11/27] - in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object --- FlatCAMApp.py | 2 +- README.md | 4 ++++ flatcamTools/ToolPaint.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 9f109c2e..0c34664b 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -93,7 +93,7 @@ class App(QtCore.QObject): # Version version = 8.907 - version_date = "2019/02/6" + version_date = "2019/02/7" beta = True # current date now diff --git a/README.md b/README.md index 17c50f26..9b782583 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +7.02.2019 + +- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object + 6.02.2019 - fixed the units calculators crash FlatCAM when using comma as decimal separator diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py index 0beb996b..a0aa5bc5 100644 --- a/flatcamTools/ToolPaint.py +++ b/flatcamTools/ToolPaint.py @@ -750,7 +750,9 @@ class ToolPaint(FlatCAMTool, Gerber): overlap=overlap, connect=connect, contour=contour) + self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) + self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_connect('mouse_press', doit) def paint_poly(self, obj, inside_pt, tooldia, overlap, From 6c6a802afbfb91e91d8c401766d28e8a085abdf4 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 01:36:37 +0200 Subject: [PATCH 12/27] - commented some debug messages - imported speedups for shapely --- FlatCAMApp.py | 4 ++-- ObjectCollection.py | 2 +- README.md | 2 ++ camlib.py | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 0c34664b..62a791ba 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -4228,8 +4228,8 @@ class App(QtCore.QObject): self.app_cursor.enabled = False try: - App.log.debug('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % ( - event.button, event.pos[0], event.pos[1], self.pos[0], self.pos[1])) + # App.log.debug('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % ( + # event.button, event.pos[0], event.pos[1], self.pos[0], self.pos[1])) modifiers = QtWidgets.QApplication.keyboardModifiers() if event.button == 1: diff --git a/ObjectCollection.py b/ObjectCollection.py index 9bcd89c2..7954fa8b 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -684,7 +684,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): :rtype: list """ - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()") + # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()") return [x.options['name'] for x in self.get_list()] def get_bounds(self): diff --git a/README.md b/README.md index 9b782583..eb8c925f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ CAD program, and create G-Code for Isolation routing. 7.02.2019 - in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object +- commented some debug messages +- imported speedups for shapely 6.02.2019 diff --git a/camlib.py b/camlib.py index 3e89a412..83f56786 100644 --- a/camlib.py +++ b/camlib.py @@ -31,6 +31,7 @@ from shapely.wkt import loads as sloads from shapely.wkt import dumps as sdumps from shapely.geometry.base import BaseGeometry from shapely.geometry import shape +from shapely import speedups from collections import Iterable From ab026a1a9d47985774768f30a2fcf40151ad67b5 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 03:16:29 +0200 Subject: [PATCH 13/27] - added a disable menu entry in the canvas contextual menu --- FlatCAMApp.py | 2 ++ FlatCAMGUI.py | 2 ++ README.md | 1 + 3 files changed, 5 insertions(+) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 62a791ba..7b6319d7 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1132,6 +1132,8 @@ class App(QtCore.QObject): self.ui.shell_btn.triggered.connect(self.on_toggle_shell) # Context Menu + self.ui.popmenu_disable.triggered.connect(lambda: self.disable_plots(self.collection.get_selected())) + self.ui.popmenu_new_geo.triggered.connect(lambda: self.new_object('geometry', 'new_g', lambda x, y: None)) self.ui.popmenu_new_exc.triggered.connect(self.new_excellon_object) self.ui.popmenu_new_prj.triggered.connect(self.on_file_new) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index ec7586d1..db7ea595 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1160,6 +1160,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ############################################################## self.popMenu = QtWidgets.QMenu() + self.popmenu_disable = self.popMenu.addAction(QtGui.QIcon('share/clear_plot32.png'), "Disable") + self.popMenu.addSeparator() self.cmenu_newmenu = self.popMenu.addMenu(QtGui.QIcon('share/file32.png'), "New") self.popmenu_new_geo = self.cmenu_newmenu.addAction(QtGui.QIcon('share/new_geo32_bis.png'), "Geo Obj") self.popmenu_new_exc = self.cmenu_newmenu.addAction(QtGui.QIcon('share/new_exc32.png'), "Exc. Obj") diff --git a/README.md b/README.md index eb8c925f..575fc938 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ CAD program, and create G-Code for Isolation routing. - in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object - commented some debug messages - imported speedups for shapely +- added a disable menu entry in the canvas contextual menu 6.02.2019 From 07ae0c9c8b28bde68b3bda470b8b0b42228d35bc Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 17:03:06 +0200 Subject: [PATCH 14/27] - small changes in Tools layout --- FlatCAM.py | 2 -- FlatCAMGUI.py | 6 +++--- README.md | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/FlatCAM.py b/FlatCAM.py index 28eb08bf..489af513 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -5,7 +5,6 @@ from PyQt5 import QtGui, QtCore, QtWidgets from FlatCAMApp import App from multiprocessing import freeze_support import VisPyPatches -import qtmodern.styles, qtmodern.windows if sys.platform == "win32": # cx_freeze 'module win32' workaround @@ -35,6 +34,5 @@ if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) fc = App() - # qtmodern.styles.dark(app) sys.exit(app.exec_()) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index db7ea595..91737671 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1942,13 +1942,13 @@ class ToolsPreferencesUI(QtWidgets.QWidget): self.tools_paint_group.setFixedWidth(200) self.tools_cutout_group = ToolsCutoutPrefGroupUI() - self.tools_cutout_group.setFixedWidth(200) + self.tools_cutout_group.setFixedWidth(220) self.tools_2sided_group = Tools2sidedPrefGroupUI() - self.tools_2sided_group.setFixedWidth(200) + self.tools_2sided_group.setFixedWidth(220) self.tools_film_group = ToolsFilmPrefGroupUI() - self.tools_film_group.setFixedWidth(200) + self.tools_film_group.setFixedWidth(220) self.tools_panelize_group = ToolsPanelizePrefGroupUI() self.tools_panelize_group.setFixedWidth(200) diff --git a/README.md b/README.md index 575fc938..d4fd47e5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ CAD program, and create G-Code for Isolation routing. - commented some debug messages - imported speedups for shapely - added a disable menu entry in the canvas contextual menu +- small changes in Tools layout 6.02.2019 From c88830314605b8af5f4369f465acbd790777a976 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 20:39:37 +0200 Subject: [PATCH 15/27] - added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section - changed the Shortcut list shortcut key to F3 --- FlatCAMApp.py | 6 ++++++ FlatCAMGUI.py | 28 +++++++++++++++++----------- README.md | 3 +++ share/about32.png | Bin 0 -> 1838 bytes share/youtube32.png | Bin 0 -> 637 bytes 5 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 share/about32.png create mode 100644 share/youtube32.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 7b6319d7..d082602a 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2896,6 +2896,12 @@ class App(QtCore.QObject): else: self.ui.plot_tab_area.closeTab(0) + def on_toggle_notebook(self): + if self.ui.splitter.sizes()[0] == 0: + self.ui.splitter.setSizes([1, 1]) + else: + self.ui.splitter.setSizes([0, 1]) + def on_toggle_axis(self): self.report_usage("on_toggle_axis()") diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 91737671..b6bb0bef 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -286,12 +286,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ### Help ### self.menuhelp = self.menu.addMenu('&Help') - self.menuhelp_about = self.menuhelp.addAction(QtGui.QIcon('share/tv16.png'), 'About FlatCAM') - self.menuhelp_home = self.menuhelp.addAction(QtGui.QIcon('share/home16.png'), 'Home') - self.menuhelp_manual = self.menuhelp.addAction(QtGui.QIcon('share/globe16.png'), 'Manual\tF1') + self.menuhelp_manual = self.menuhelp.addAction(QtGui.QIcon('share/globe16.png'), 'Help\tF1') + self.menuhelp_home = self.menuhelp.addAction(QtGui.QIcon('share/home16.png'), 'FlatCAM.org') self.menuhelp.addSeparator() - self.menuhelp_shortcut_list = self.menuhelp.addAction(QtGui.QIcon('share/shortcuts24.png'), 'Shortcuts List\t`') - self.menuhelp_videohelp = self.menuhelp.addAction(QtGui.QIcon('share/videohelp24.png'), 'See on YouTube\tF2') + self.menuhelp_videohelp = self.menuhelp.addAction(QtGui.QIcon('share/youtube32.png'), 'YouTube Channel\tF2') + self.menuhelp_shortcut_list = self.menuhelp.addAction(QtGui.QIcon('share/shortcuts24.png'), 'Shortcuts List\tF3') + self.menuhelp_about = self.menuhelp.addAction(QtGui.QIcon('share/about32.png'), 'About') ### FlatCAM Editor menu ### @@ -757,7 +757,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): - + @@ -984,6 +984,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): + + + + @@ -1665,6 +1669,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_F2 or key == 'F2': webbrowser.open(self.app.video_url) + # Show shortcut list + if key == QtCore.Qt.Key_F3 or key == 'F3': + self.app.on_shortcut_list() + # Switch to Project Tab if key == QtCore.Qt.Key_1: self.app.on_select_tab('project') @@ -1768,12 +1776,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_Minus: self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) - # Show shortcut list - if key == QtCore.Qt.Key_Ampersand: - self.app.on_shortcut_list() - + # toggle display of Notebook area if key == QtCore.Qt.Key_QuoteLeft: - self.app.on_shortcut_list() + self.app.on_toggle_notebook() + return def dragEnterEvent(self, event): diff --git a/README.md b/README.md index d4fd47e5..3363d5bf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ CAD program, and create G-Code for Isolation routing. - imported speedups for shapely - added a disable menu entry in the canvas contextual menu - small changes in Tools layout +- added some new icons in the help menu and reorganized this menu +- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section +- changed the Shortcut list shortcut key to F3 6.02.2019 diff --git a/share/about32.png b/share/about32.png new file mode 100644 index 0000000000000000000000000000000000000000..48bf9f6f38c404d86774de95515513c97609bafb GIT binary patch literal 1838 zcmV+}2hsS6P)yQnVE7r=o%% zG}bB-!4E|XNgMp&(xyhRX-TTmrYOOLG-+O%bTzvryV=d&P4>RO%^W|>+?lz1HyV53 zuy>X-=l_5H&;NPO8O3dm{KE$)z&wDc?Fzj)m&SE0h5@pf6!jLAYc8(qp#_ZBX1P?OaI?(fQnA=n@#WEe$QBLbYvY@uQ!-mYO!#$-L=jHfBRC3v<*ocZZ4OY zUn~^srP*hujyydHG;gKIss!@8Up=sG%g__|@7T(}=9)~+HfXg?*BS$$RQCo7l=6ur z#AETs5~bqYYnR^G^CVDNO^qG``Q5J`*m+<6iIL$AoVrwIx#ALs?f~C6o&gl6E|;Ti z!rc_2ASF<(6}egX_Zye~`1q4RaU}_TEAZoIi&Pt454ci_=e{w_^LsY()z1%*O>6#s zp#q{nDG)&^|91<8Whb$m%-z|sU3V2Oy#5x@BFY>Rfxj}bZOc#y-fEk0l>v-W{>H%g zXeuN%KAHkisLoVDYJz81DRiozWNzcv#vXj}n?NRxE8x35n@#V(f5%o%T`Kp0hq+dO zDCU-$A%VH27AnZS4=RWbe(Pu^vunu=-TOjr>`Qk7PFD&(Gye7GckbBsl|tF#>SC)4 zT!r8&oTomnGQH5`-LoZLKC*}si7B9bMU+HoQU0^mwqvE%>!pwWbR1{^#sbJ@Q_pT3 zS;w(|F84%E2p%Y*6y>_dnwZ5tKV3(3Z}ZfaL<$5(T$u&~U<{5Dw4L4nBmiyYa+yt) zTGX66(zjg0a=C?TKy*-AS|M6kIm8kqMnF2%&>>;`vNi^6ZAhg12k}c$TcBurURU(x zs!OrzB3gwtEg~U6j{*Tf(f&FmF$EN1-!q=UaZD!&4Tx1NRcEv^n>x`i6z8E3}l5; zxkMW#Ufe--;X+oKB|4eawH^>NNCn(HS^cY zl`BVo^f#aiv;=53>cyApjVk@AC~qoa5Ji6r1v=)8t6&Au+kp}r1~i4GI>p&@hXe2m z&=$a}TsyU|S}7H?34^V>Tj|Q#eZu%7EYlbX%Z^B7`!wrwaQ!zdy)}kLIE@1?jMCO){y;4{1I;U*y^c zhksR_KQ{~92s{wpOx&p>Pfs?g*I&b{lF@+^0ww&D@xR{SgY~{gL_kEa9mU-vnwjY` z7f+u%cIE9K90L}C62aXtzN-Lz;}gF>V5c`txGGEAQh~8_blBLD$#iHo9p&5>EXN|- zrx_p8T$wI&{OHMJXMX$OUSJNG1u+TS@A)K=>Hi{1 zJ4!of3Cc8fCdAK&{*T_ z*Wdg2cbqj_9uudp&q<_6ISCR;jh)aq2_c;lk_koI^;j-9nV&CJigV`;PC3m9ZB9x%gi` cxBYqkAGY6f`z8K zq!=hIE(#@qlP(So4MDUvJNbX;rvJblf`-VcA;?K*39c9}4Q7gDC0^sISFi8ebT0`G z-gD&|9E6pMTH0_#IVkC8qA-K|))yKhIt(}%08 zD_R~IX>;jatKk(#m8p*VejnO+lPuJummZnYl9qtPEe4{~2Q}UCO^v=)IK0cngfVt=8 z(2;dO($`1#>}=*^3FwfdBx`{hO&rS&H7l=?@mTEA3Y;P0!ml>H{dH%hLa9(-zfd45 zl?!DAp16Cp*?Vxfw8ZZ8G}W!GhH?%kiQoX}%p$ICZn8Tw!{O3WmNdg>K$h)3 zcaB*p!TlG~gNuf;ZufbZ+0O*&dn2Kpsi~&Lb=f6F&UN83-!<>zMh1Q;M9A7-~Aq66tB z?x{g)(B@JiX!F{a)ZHbWI#hvUK?V0C%nk#}7V#UiQoxRxEvFH0dRA7-EnDz4|1^I9 X)kodu!KzTv00000NkvXXu0mjf+cqTF literal 0 HcmV?d00001 From 5b75d416ecff3dba8f8338acde45145b9cfa8786 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 22:37:51 +0200 Subject: [PATCH 16/27] - moved some graphical classes out of Tool Shell to GUIElements.py where they belong - when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed - in Move Tool I've added the type of object that was moved in the status bar message - color coded the status bar bullet to blue for selection - the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs. --- FlatCAMApp.py | 60 ++++++++++++- FlatCAMGUI.py | 8 +- GUIElements.py | 161 ++++++++++++++++++++++++++++++++++- README.md | 6 ++ flatcamTools/ToolMove.py | 3 +- flatcamTools/ToolShell.py | 173 +++----------------------------------- share/bluelight12.png | Bin 0 -> 313 bytes 7 files changed, 245 insertions(+), 166 deletions(-) create mode 100644 share/bluelight12.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index d082602a..5c1a9570 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1700,7 +1700,7 @@ class App(QtCore.QObject): """ pass - def shell_message(self, msg, show=False, error=False, warning=False, success=False): + def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False): """ Shows a message on the FlatCAM Shell @@ -1720,8 +1720,10 @@ class App(QtCore.QObject): else: if success: self.shell.append_success(msg + "\n") - else: - self.shell.append_output(msg + "\n") + if success: + self.shell.append_selected(msg + "\n") + else: + self.shell.append_output(msg + "\n") except AttributeError: log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg)) @@ -1894,13 +1896,19 @@ class App(QtCore.QObject): elif level.lower() == "error_notcl": self.shell_message(msg, error=True, show=False) + elif level.lower() == "warning_notcl": self.shell_message(msg, warning=True, show=False) elif level.lower() == "success": self.shell_message(msg, success=True, show=False) + + elif level.lower() == "selected": + self.shell_message(msg, selected=True, show=False) + else: self.shell_message(msg, show=False) + else: self.ui.fcinfo.set_status(str(msg), level="info") @@ -4440,6 +4448,9 @@ class App(QtCore.QObject): # and as a convenience move the focus to the Project tab because Selected tab is now empty self.ui.notebook.setCurrentWidget(self.ui.project_tab) + # delete any text in the status bar, implicitly the last object name that was selected + self.inform.emit("") + else: # case when there is only an object under the click and we toggle it if len(objects_under_the_click_list) == 1: @@ -4448,6 +4459,20 @@ class App(QtCore.QObject): # create the selection box around the selected object curr_sel_obj = self.collection.get_active() self.draw_selection_shape(curr_sel_obj) + + if curr_sel_obj.kind == 'gerber': + self.inform.emit('[selected]%s selected' % + ('green', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'excellon': + self.inform.emit('[selected]%s selected' % + ('brown', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'cncjob': + self.inform.emit('[selected]%s selected' % + ('blue', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'geometry': + self.inform.emit('[selected]%s selected' % + ('red', str(curr_sel_obj.options['name']))) + elif self.collection.get_active().options['name'] not in objects_under_the_click_list: self.collection.set_all_inactive() self.delete_selection_shape() @@ -4455,9 +4480,25 @@ class App(QtCore.QObject): # create the selection box around the selected object curr_sel_obj = self.collection.get_active() self.draw_selection_shape(curr_sel_obj) + + if curr_sel_obj.kind == 'gerber': + self.inform.emit('[selected]%s selected' % + ('green', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'excellon': + self.inform.emit('[selected]%s selected' % + ('brown', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'cncjob': + self.inform.emit('[selected]%s selected' % + ('blue', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'geometry': + self.inform.emit('[selected]%s selected' % + ('red', str(curr_sel_obj.options['name']))) + else: self.collection.set_all_inactive() self.delete_selection_shape() + self.inform.emit("") + else: # If there is no selected object # make active the first element of the overlapped objects list @@ -4482,6 +4523,19 @@ class App(QtCore.QObject): # create the selection box around the selected object self.draw_selection_shape(curr_sel_obj) + if curr_sel_obj.kind == 'gerber': + self.inform.emit('[selected]%s selected' % + ('green', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'excellon': + self.inform.emit('[selected]%s selected' % + ('brown', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'cncjob': + self.inform.emit('[selected]%s selected' % + ('blue', str(curr_sel_obj.options['name']))) + elif curr_sel_obj.kind == 'geometry': + self.inform.emit('[selected]%s selected' % + ('red', str(curr_sel_obj.options['name']))) + # for obj in self.collection.get_list(): # obj.plot() # curr_sel_obj.plot(color=self.FC_dark_blue, face_color=self.FC_light_blue) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index b6bb0bef..024ef608 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -4016,9 +4016,11 @@ class FlatCAMInfoBar(QtWidgets.QWidget): layout.addStretch() - def set_text_(self, text): + def set_text_(self, text, color=None): self.text.setText(text) self.text.setToolTip(text) + if color: + self.text.setStyleSheet('color: %s' % str(color)) def set_status(self, text, level="info"): level = str(level) @@ -4029,9 +4031,11 @@ class FlatCAMInfoBar(QtWidgets.QWidget): self.pmap = QtGui.QPixmap('share/greenlight12.png') elif level == "WARNING" or level == "WARNING_NOTCL": self.pmap = QtGui.QPixmap('share/yellowlight12.png') + elif level == "selected" or level == "SELECTED": + self.pmap = QtGui.QPixmap('share/bluelight12.png') else: self.pmap = QtGui.QPixmap('share/graylight12.png') - self.icon.setPixmap(self.pmap) self.set_text_(text) + self.icon.setPixmap(self.pmap) # end of file diff --git a/GUIElements.py b/GUIElements.py index d2242559..54dc0c54 100644 --- a/GUIElements.py +++ b/GUIElements.py @@ -1,14 +1,18 @@ from PyQt5 import QtGui, QtCore, QtWidgets -from PyQt5.QtCore import pyqtSignal, pyqtSlot +from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot +from PyQt5.QtWidgets import QTextEdit, QCompleter, QAction +from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor from copy import copy import re import logging +import html log = logging.getLogger('base') EDIT_SIZE_HINT = 70 + class RadioSet(QtWidgets.QWidget): activated_custom = QtCore.pyqtSignal() @@ -1218,3 +1222,158 @@ class Dialog_box(QtWidgets.QWidget): self.location, self.ok = dialog_box.getText(self, title, label) + +class _BrowserTextEdit(QTextEdit): + + def __init__(self, version): + QTextEdit.__init__(self) + self.menu = None + self.version = version + + def contextMenuEvent(self, event): + self.menu = self.createStandardContextMenu(event.pos()) + clear_action = QAction("Clear", self) + clear_action.setShortcut(QKeySequence(Qt.Key_Delete)) # it's not working, the shortcut + self.menu.addAction(clear_action) + clear_action.triggered.connect(self.clear) + self.menu.exec_(event.globalPos()) + + + def clear(self): + QTextEdit.clear(self) + text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version + text = html.escape(text) + text = text.replace('\n', '
') + self.moveCursor(QTextCursor.End) + self.insertHtml(text) + + +class _ExpandableTextEdit(QTextEdit): + """ + Class implements edit line, which expands themselves automatically + """ + + historyNext = pyqtSignal() + historyPrev = pyqtSignal() + + def __init__(self, termwidget, *args): + QTextEdit.__init__(self, *args) + self.setStyleSheet("font: 9pt \"Courier\";") + self._fittedHeight = 1 + self.textChanged.connect(self._fit_to_document) + self._fit_to_document() + self._termWidget = termwidget + + self.completer = MyCompleter() + + self.model = QtCore.QStringListModel() + self.completer.setModel(self.model) + self.set_model_data(keyword_list=[]) + self.completer.insertText.connect(self.insertCompletion) + + def set_model_data(self, keyword_list): + self.model.setStringList(keyword_list) + + def insertCompletion(self, completion): + tc = self.textCursor() + extra = (len(completion) - len(self.completer.completionPrefix())) + tc.movePosition(QTextCursor.Left) + tc.movePosition(QTextCursor.EndOfWord) + tc.insertText(completion[-extra:]) + self.setTextCursor(tc) + self.completer.popup().hide() + + def focusInEvent(self, event): + if self.completer: + self.completer.setWidget(self) + QTextEdit.focusInEvent(self, event) + + def keyPressEvent(self, event): + """ + Catch keyboard events. Process Enter, Up, Down + """ + if event.matches(QKeySequence.InsertParagraphSeparator): + text = self.toPlainText() + if self._termWidget.is_command_complete(text): + self._termWidget.exec_current_command() + return + elif event.matches(QKeySequence.MoveToNextLine): + text = self.toPlainText() + cursor_pos = self.textCursor().position() + textBeforeEnd = text[cursor_pos:] + + if len(textBeforeEnd.split('\n')) <= 1: + self.historyNext.emit() + return + elif event.matches(QKeySequence.MoveToPreviousLine): + text = self.toPlainText() + cursor_pos = self.textCursor().position() + text_before_start = text[:cursor_pos] + # lineCount = len(textBeforeStart.splitlines()) + line_count = len(text_before_start.split('\n')) + if len(text_before_start) > 0 and \ + (text_before_start[-1] == '\n' or text_before_start[-1] == '\r'): + line_count += 1 + if line_count <= 1: + self.historyPrev.emit() + return + elif event.matches(QKeySequence.MoveToNextPage) or \ + event.matches(QKeySequence.MoveToPreviousPage): + return self._termWidget.browser().keyPressEvent(event) + + tc = self.textCursor() + if event.key() == Qt.Key_Tab and self.completer.popup().isVisible(): + self.completer.insertText.emit(self.completer.getSelected()) + self.completer.setCompletionMode(QCompleter.PopupCompletion) + return + + QTextEdit.keyPressEvent(self, event) + tc.select(QTextCursor.WordUnderCursor) + cr = self.cursorRect() + + if len(tc.selectedText()) > 0: + self.completer.setCompletionPrefix(tc.selectedText()) + popup = self.completer.popup() + popup.setCurrentIndex(self.completer.completionModel().index(0, 0)) + + cr.setWidth(self.completer.popup().sizeHintForColumn(0) + + self.completer.popup().verticalScrollBar().sizeHint().width()) + self.completer.complete(cr) + else: + self.completer.popup().hide() + + def sizeHint(self): + """ + QWidget sizeHint impelemtation + """ + hint = QTextEdit.sizeHint(self) + hint.setHeight(self._fittedHeight) + return hint + + def _fit_to_document(self): + """ + Update widget height to fit all text + """ + documentsize = self.document().size().toSize() + self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height()) + self.setMaximumHeight(self._fittedHeight) + self.updateGeometry() + + def insertFromMimeData(self, mime_data): + # Paste only plain text. + self.insertPlainText(mime_data.text()) + + +class MyCompleter(QCompleter): + insertText = pyqtSignal(str) + + def __init__(self, parent=None): + QCompleter.__init__(self) + self.setCompletionMode(QCompleter.PopupCompletion) + self.highlighted.connect(self.setHighlighted) + + def setHighlighted(self, text): + self.lastSelected = text + + def getSelected(self): + return self.lastSelected diff --git a/README.md b/README.md index 3363d5bf..8919f409 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ CAD program, and create G-Code for Isolation routing. - added some new icons in the help menu and reorganized this menu - added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section - changed the Shortcut list shortcut key to F3 +- moved some graphical classes out of Tool Shell to GUIElements.py where they belong +- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed +- in Move Tool I've added the type of object that was moved in the status bar message +- color coded the status bar bullet to blue for selection +- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs. + 6.02.2019 diff --git a/flatcamTools/ToolMove.py b/flatcamTools/ToolMove.py index 562aff80..9f920af4 100644 --- a/flatcamTools/ToolMove.py +++ b/flatcamTools/ToolMove.py @@ -139,12 +139,13 @@ class ToolMove(FlatCAMTool): proc.done() # delete the selection bounding box self.delete_shape() + self.app.inform.emit('[success]%s object was moved ...' % + str(sel_obj.kind).capitalize()) self.app.worker_task.emit({'fcn': job_move, 'params': [self]}) self.clicked_move = 0 self.toggle() - self.app.inform.emit("[success]Object was moved ...") return except TypeError: diff --git a/flatcamTools/ToolShell.py b/flatcamTools/ToolShell.py index 80e94437..22171252 100644 --- a/flatcamTools/ToolShell.py +++ b/flatcamTools/ToolShell.py @@ -6,165 +6,12 @@ # MIT Licence # ############################################################ +# from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QTextCursor +from PyQt5.QtWidgets import QVBoxLayout, QWidget +from GUIElements import _BrowserTextEdit, _ExpandableTextEdit import html -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtCore import Qt, QStringListModel -from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor -from PyQt5.QtWidgets import QLineEdit, QSizePolicy, QTextEdit, QVBoxLayout, QWidget, QCompleter, QAction - -class _BrowserTextEdit(QTextEdit): - - def __init__(self, version): - QTextEdit.__init__(self) - self.menu = None - self.version = version - - def contextMenuEvent(self, event): - self.menu = self.createStandardContextMenu(event.pos()) - clear_action = QAction("Clear", self) - clear_action.setShortcut(QKeySequence(Qt.Key_Delete)) # it's not working, the shortcut - self.menu.addAction(clear_action) - clear_action.triggered.connect(self.clear) - self.menu.exec_(event.globalPos()) - - - def clear(self): - QTextEdit.clear(self) - text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version - text = html.escape(text) - text = text.replace('\n', '
') - self.moveCursor(QTextCursor.End) - self.insertHtml(text) - -class _ExpandableTextEdit(QTextEdit): - """ - Class implements edit line, which expands themselves automatically - """ - - historyNext = pyqtSignal() - historyPrev = pyqtSignal() - - def __init__(self, termwidget, *args): - QTextEdit.__init__(self, *args) - self.setStyleSheet("font: 9pt \"Courier\";") - self._fittedHeight = 1 - self.textChanged.connect(self._fit_to_document) - self._fit_to_document() - self._termWidget = termwidget - - self.completer = MyCompleter() - - self.model = QStringListModel() - self.completer.setModel(self.model) - self.set_model_data(keyword_list=[]) - self.completer.insertText.connect(self.insertCompletion) - - def set_model_data(self, keyword_list): - self.model.setStringList(keyword_list) - - def insertCompletion(self, completion): - tc = self.textCursor() - extra = (len(completion) - len(self.completer.completionPrefix())) - tc.movePosition(QTextCursor.Left) - tc.movePosition(QTextCursor.EndOfWord) - tc.insertText(completion[-extra:]) - self.setTextCursor(tc) - self.completer.popup().hide() - - def focusInEvent(self, event): - if self.completer: - self.completer.setWidget(self) - QTextEdit.focusInEvent(self, event) - - def keyPressEvent(self, event): - """ - Catch keyboard events. Process Enter, Up, Down - """ - if event.matches(QKeySequence.InsertParagraphSeparator): - text = self.toPlainText() - if self._termWidget.is_command_complete(text): - self._termWidget.exec_current_command() - return - elif event.matches(QKeySequence.MoveToNextLine): - text = self.toPlainText() - cursor_pos = self.textCursor().position() - textBeforeEnd = text[cursor_pos:] - - if len(textBeforeEnd.split('\n')) <= 1: - self.historyNext.emit() - return - elif event.matches(QKeySequence.MoveToPreviousLine): - text = self.toPlainText() - cursor_pos = self.textCursor().position() - text_before_start = text[:cursor_pos] - # lineCount = len(textBeforeStart.splitlines()) - line_count = len(text_before_start.split('\n')) - if len(text_before_start) > 0 and \ - (text_before_start[-1] == '\n' or text_before_start[-1] == '\r'): - line_count += 1 - if line_count <= 1: - self.historyPrev.emit() - return - elif event.matches(QKeySequence.MoveToNextPage) or \ - event.matches(QKeySequence.MoveToPreviousPage): - return self._termWidget.browser().keyPressEvent(event) - - tc = self.textCursor() - if event.key() == Qt.Key_Tab and self.completer.popup().isVisible(): - self.completer.insertText.emit(self.completer.getSelected()) - self.completer.setCompletionMode(QCompleter.PopupCompletion) - return - - QTextEdit.keyPressEvent(self, event) - tc.select(QTextCursor.WordUnderCursor) - cr = self.cursorRect() - - if len(tc.selectedText()) > 0: - self.completer.setCompletionPrefix(tc.selectedText()) - popup = self.completer.popup() - popup.setCurrentIndex(self.completer.completionModel().index(0, 0)) - - cr.setWidth(self.completer.popup().sizeHintForColumn(0) - + self.completer.popup().verticalScrollBar().sizeHint().width()) - self.completer.complete(cr) - else: - self.completer.popup().hide() - - def sizeHint(self): - """ - QWidget sizeHint impelemtation - """ - hint = QTextEdit.sizeHint(self) - hint.setHeight(self._fittedHeight) - return hint - - def _fit_to_document(self): - """ - Update widget height to fit all text - """ - documentsize = self.document().size().toSize() - self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height()) - self.setMaximumHeight(self._fittedHeight) - self.updateGeometry() - - def insertFromMimeData(self, mime_data): - # Paste only plain text. - self.insertPlainText(mime_data.text()) - - -class MyCompleter(QCompleter): - insertText = pyqtSignal(str) - - def __init__(self, parent=None): - QCompleter.__init__(self) - self.setCompletionMode(QCompleter.PopupCompletion) - self.highlighted.connect(self.setHighlighted) - - def setHighlighted(self, text): - self.lastSelected = text - - def getSelected(self): - return self.lastSelected class TermWidget(QWidget): @@ -234,7 +81,7 @@ class TermWidget(QWidget): """ Convert text to HTML for inserting it to browser """ - assert style in ('in', 'out', 'err', 'warning', 'success') + assert style in ('in', 'out', 'err', 'warning', 'success', 'selected') text = html.escape(text) text = text.replace('\n', '
') @@ -247,6 +94,8 @@ class TermWidget(QWidget): text = '%s' % text elif style == 'success': text = '%s' % text + elif style == 'selected': + text = '%s' % text else: text = '%s' % text # without span
is ignored!!! @@ -313,6 +162,11 @@ class TermWidget(QWidget): """ self._append_to_browser('success', text) + def append_selected(self, text): + """Appent text to output widget + """ + self._append_to_browser('selected', text) + def append_warning(self, text): """Appent text to output widget """ @@ -352,6 +206,7 @@ class TermWidget(QWidget): self._edit.setPlainText(self._history[self._historyIndex]) self._edit.moveCursor(QTextCursor.End) + class FCShell(TermWidget): def __init__(self, sysShell, version, *args): TermWidget.__init__(self, version, *args) diff --git a/share/bluelight12.png b/share/bluelight12.png new file mode 100644 index 0000000000000000000000000000000000000000..050aa9063349fd1e25e4d294e7aeb1a03518b971 GIT binary patch literal 313 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$3?vg*uel1O7>k44ofvPP)Tsw@I14-?i-D9C z2s3_2ndAi&^b7C_aRt&4z{aPGfQYArt!3>OPyFN%C|NEcm=;S5Y^K@|xshAVm z=PuM>z{8yC;lUiFc=Eq}SLWdj>TCHYFGUQ`%^Yb#!n$)n-?|e_$wS>s^XSV5m z@K5_JFu!Aei$Bw%D((=Lr8VKtk8Do8eg0yt_H*u#==W9IXI!{+W Jmvv4FO#nafSDgR= literal 0 HcmV?d00001 From 0bc018bbe01dc9bc4f33571d657af716b411dc29 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 7 Feb 2019 23:13:11 +0200 Subject: [PATCH 17/27] - added an empty message when deselection is done by shortcut key Escape --- FlatCAMGUI.py | 1 + 1 file changed, 1 insertion(+) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 024ef608..932cc9c7 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1695,6 +1695,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Escape = Deselect All if key == QtCore.Qt.Key_Escape or key == 'Escape': self.app.on_deselect_all() + self.app.inform.emit("") # Space = Toggle Active/Inactive if key == QtCore.Qt.Key_Space: From f39fea072cfc15600a9adbe53023c3de6c911d49 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 8 Feb 2019 14:55:50 +0200 Subject: [PATCH 18/27] - when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible - changed the menu entry Toggle Grid name to Toggle Grid Snap --- FlatCAMApp.py | 4 ++++ FlatCAMGUI.py | 2 +- README.md | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 5c1a9570..47ec08c8 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -4199,6 +4199,10 @@ class App(QtCore.QObject): elif name == 'tool': self.ui.notebook.setCurrentWidget(self.ui.tool_tab) + # if the splitter us hidden, display it + if self.ui.splitter.sizes()[0] == 0: + self.ui.splitter.setSizes([1, 1]) + def on_copy_name(self): self.report_usage("on_copy_name()") diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 932cc9c7..497818b5 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -273,7 +273,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): QtGui.QIcon('share/plot32.png'), "&Toggle Plot Area\tCTRL+F10") self.menuview.addSeparator() - self.menuview_toggle_grid = self.menuview.addAction(QtGui.QIcon('share/grid32.png'), "&Toggle Grid\tG") + self.menuview_toggle_grid = self.menuview.addAction(QtGui.QIcon('share/grid32.png'), "&Toggle Grid Snap\tG") self.menuview_toggle_axis = self.menuview.addAction(QtGui.QIcon('share/axis32.png'), "&Toggle Axis\tSHIFT+G") self.menuview_toggle_workspace = self.menuview.addAction(QtGui.QIcon('share/workspace24.png'), "Toggle Workspace\tSHIFT+W") diff --git a/README.md b/README.md index 8919f409..566bea2c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ CAD program, and create G-Code for Isolation routing. ================================================= +8.02.2019 + +- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible +- changed the menu entry Toggle Grid name to Toggle Grid Snap + + 7.02.2019 - in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object @@ -25,7 +31,6 @@ CAD program, and create G-Code for Isolation routing. - color coded the status bar bullet to blue for selection - the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs. - 6.02.2019 - fixed the units calculators crash FlatCAM when using comma as decimal separator From 2524e6bd59495126f1b0a96b725b9d566871ccaa Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 8 Feb 2019 15:24:47 +0200 Subject: [PATCH 19/27] - minor GUI changes --- FlatCAMApp.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 47ec08c8..bd059479 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -92,7 +92,7 @@ class App(QtCore.QObject): log.addHandler(handler) # Version - version = 8.907 + version = 8.908 version_date = "2019/02/7" beta = True @@ -6426,11 +6426,11 @@ class App(QtCore.QObject): The normal flow when working in FlatCAM is the following:

    -
  1. Loat/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into FlatCAM using either the menu's, toolbars or key shortcuts.
    +
  2. Loat/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into FlatCAM using either the menu's, toolbars, key shortcuts or even dragging and dropping the files on the GUI.

    You can also load a FlatCAM project by double clicking on the project file, drag & drop of the file into the FLATCAM GUI or through the menu/toolbar links offered within the app.

     
  3. -
  4. Once an object is available in the Project Tab, by selecting it and then selecting SELECTED TAB (more simpler is to double click the object name in the Project Tab), SELECTED TAB will be updated with the object properties according to it's kind: Gerber, Excellon, Geometry or CNCJob object.
    +
  5. Once an object is available in the Project Tab, by selecting it and then focusing on SELECTED TAB (more simpler is to double click the object name in the Project Tab), SELECTED TAB will be updated with the object properties according to it's kind: Gerber, Excellon, Geometry or CNCJob object.

    If the selection of the object is done on the canvas by single click instead, and the SELECTED TAB is in focus, again the object properties will be displayed into the Selected Tab. Alternatively, double clicking on the object on the canvas will bring the SELECTED TAB and populate it even if it was out of focus.

    @@ -6439,7 +6439,7 @@ The normal flow when working in FlatCAM is the following:

    Gerber/Excellon Object -> Change Param -> Generate Geometry -> Geometry Object -> Add tools (change param in Selected Tab) -> Generate CNCJob -> CNCJob Object -> Verify GCode (through Edit CNC Code) and/or append/prepend to GCode (again, done in SELECTED TAB) -> Save GCode
-

A list of key shortcuts is available through an menu entry in Help -> Shortcuts List or through it's own key shortcut: '`' (key left to 1).

+

A list of key shortcuts is available through an menu entry in Help -> Shortcuts List or through it's own key shortcut: F3.

''' From 2be35e4248284b8a56b07e4983f79676844e3cf4 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 8 Feb 2019 15:55:32 +0200 Subject: [PATCH 20/27] - fixed errors in Toggle Axis - fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View --- FlatCAMGUI.py | 4 ++-- ObjectCollection.py | 2 +- README.md | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 497818b5..67ba1ba4 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1562,7 +1562,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Toggle axis if key == QtCore.Qt.Key_G: - if self.toggle_axis is False: + if self.app.toggle_axis is False: self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) self.app.plotcanvas.redraw() @@ -1571,7 +1571,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) - self.appplotcanvas.redraw() + self.app.plotcanvas.redraw() self.app.toggle_axis = False # Open Preferences Window diff --git a/ObjectCollection.py b/ObjectCollection.py index 7954fa8b..22f9c3fd 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -36,7 +36,7 @@ class KeySensitiveListView(QtWidgets.QTreeView): keyPressed = QtCore.pyqtSignal(int) def keyPressEvent(self, event): - super(KeySensitiveListView, self).keyPressEvent(event) + # super(KeySensitiveListView, self).keyPressEvent(event) self.keyPressed.emit(event.key()) def dragEnterEvent(self, event): diff --git a/README.md b/README.md index 566bea2c..16728a38 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ CAD program, and create G-Code for Isolation routing. - when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible - changed the menu entry Toggle Grid name to Toggle Grid Snap - +- fixed errors in Toggle Axis +- fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View 7.02.2019 From 824b1a95ab374739de4e421cf73dc7bc1dac5b05 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 8 Feb 2019 16:57:51 +0200 Subject: [PATCH 21/27] - moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI --- FlatCAMEditor.py | 358 +---------------------- FlatCAMGUI.py | 739 ++++++++++++++++++++++++++++++++++------------- README.md | 1 + 3 files changed, 536 insertions(+), 562 deletions(-) diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index 86559660..3271c242 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -2135,7 +2135,6 @@ class FlatCAMGeoEditor(QtCore.QObject): # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp # but those from FlatCAMGeoEditor - self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) @@ -2146,7 +2145,6 @@ class FlatCAMGeoEditor(QtCore.QObject): self.canvas.vis_connect('mouse_press', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) - self.canvas.vis_connect('key_press', self.on_canvas_key) def disconnect_canvas_event_handlers(self): @@ -2154,11 +2152,8 @@ class FlatCAMGeoEditor(QtCore.QObject): self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) - self.canvas.vis_disconnect('key_press', self.on_canvas_key) - # we restore the key and mouse control to FlatCAMApp method - self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot) @@ -2572,211 +2567,6 @@ class FlatCAMGeoEditor(QtCore.QObject): self.tool_shape.redraw() - def on_canvas_key(self, event): - """ - event.key has the key. - - :param event: - :return: - """ - self.key = event.key.name - self.geo_key_modifiers = QtWidgets.QApplication.keyboardModifiers() - - if self.geo_key_modifiers == Qt.ControlModifier: - # save (update) the current geometry and return to the App - if self.key == 'S': - self.app.editor2object() - return - - # toggle the measurement tool - if self.key == 'M': - self.app.measurement_tool.run() - return - - # Finish the current action. Use with tools that do not - # complete automatically, like a polygon or path. - if event.key.name == 'Enter': - if isinstance(self.active_tool, FCShapeTool): - self.active_tool.click(self.snap(self.x, self.y)) - self.active_tool.make() - if self.active_tool.complete: - self.on_shape_complete() - self.app.inform.emit("[success]Done.") - # automatically make the selection tool active after completing current action - self.select_tool('select') - return - - # Abort the current action - if event.key.name == 'Escape': - # TODO: ...? - # self.on_tool_select("select") - self.app.inform.emit("[WARNING_NOTCL]Cancelled.") - - self.delete_utility_geometry() - - self.replot() - # self.select_btn.setChecked(True) - # self.on_tool_select('select') - self.select_tool('select') - return - - # Delete selected object - if event.key.name == 'Delete': - self.delete_selected() - self.replot() - - # Move - if event.key.name == 'Space': - self.app.ui.geo_rotate_btn.setChecked(True) - self.on_tool_select('rotate') - self.active_tool.set_origin(self.snap(self.x, self.y)) - - if event.key == '1': - self.app.on_zoom_fit(None) - - if event.key == '2': - self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - - if event.key == '3': - self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - - # Arc Tool - if event.key.name == 'A': - self.select_tool('arc') - - # Buffer - if event.key.name == 'B': - self.select_tool('buffer') - - # Copy - if event.key.name == 'C': - self.app.ui.geo_copy_btn.setChecked(True) - self.on_tool_select('copy') - self.active_tool.set_origin(self.snap(self.x, self.y)) - self.app.inform.emit("Click on target point.") - - # Substract Tool - if event.key.name == 'E': - if self.get_selected() is not None: - self.intersection() - else: - msg = "Please select geometry items \n" \ - "on which to perform Intersection Tool." - - messagebox =QtWidgets.QMessageBox() - messagebox.setText(msg) - messagebox.setWindowTitle("Warning") - messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) - messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) - messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) - messagebox.exec_() - - # Grid Snap - if event.key.name == 'G': - self.app.ui.grid_snap_btn.trigger() - - # make sure that the cursor shape is enabled/disabled, too - if self.options['grid_snap'] is True: - self.app.app_cursor.enabled = True - else: - self.app.app_cursor.enabled = False - - # Paint - if event.key.name == 'I': - self.select_tool('paint') - - # Corner Snap - if event.key.name == 'K': - self.on_corner_snap() - - # Move - if event.key.name == 'M': - self.on_move_click() - - # Polygon Tool - if event.key.name == 'N': - self.select_tool('polygon') - - # Circle Tool - if event.key.name == 'O': - self.select_tool('circle') - - # Path Tool - if event.key.name == 'P': - self.select_tool('path') - - # Rectangle Tool - if event.key.name == 'R': - self.select_tool('rectangle') - - # Substract Tool - if event.key.name == 'S': - if self.get_selected() is not None: - self.subtract() - else: - msg = "Please select geometry items \n" \ - "on which to perform Substraction Tool." - - messagebox =QtWidgets.QMessageBox() - messagebox.setText(msg) - messagebox.setWindowTitle("Warning") - messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) - messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) - messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) - messagebox.exec_() - - # Add Text Tool - if event.key.name == 'T': - self.select_tool('text') - - # Substract Tool - if event.key.name == 'U': - if self.get_selected() is not None: - self.union() - else: - msg = "Please select geometry items \n" \ - "on which to perform union." - - messagebox =QtWidgets.QMessageBox() - messagebox.setText(msg) - messagebox.setWindowTitle("Warning") - messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) - messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) - messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) - messagebox.exec_() - - # Cut Action Tool - if event.key.name == 'X': - if self.get_selected() is not None: - self.cutpath() - else: - msg = 'Please first select a geometry item to be cutted\n' \ - 'then select the geometry item that will be cutted\n' \ - 'out of the first item. In the end press ~X~ key or\n' \ - 'the toolbar button.' \ - - messagebox =QtWidgets.QMessageBox() - messagebox.setText(msg) - messagebox.setWindowTitle("Warning") - messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) - messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) - messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) - messagebox.exec_() - - # Propagate to tool - response = None - if self.active_tool is not None: - response = self.active_tool.on_key(event.key) - if response is not None: - self.app.inform.emit(response) - - # Show Shortcut list - if event.key.name == '`': - self.app.on_shortcut_list() - - def on_canvas_key_release(self, event): - self.key = None - def on_delete_btn(self): self.delete_selected() self.replot() @@ -4277,34 +4067,27 @@ class FlatCAMExcEditor(QtCore.QObject): # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp # but those from FlatCAMGeoEditor - self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent) + self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot) - self.app.collection.view.keyPressed.disconnect() self.app.collection.view.clicked.disconnect() self.canvas.vis_connect('mouse_press', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) - self.canvas.vis_connect('key_press', self.on_canvas_key) - def disconnect_canvas_event_handlers(self): self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) - self.canvas.vis_disconnect('key_press', self.on_canvas_key) - self.canvas.vis_disconnect('key_release', self.on_canvas_key_release) # we restore the key and mouse control to FlatCAMApp method - self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent) self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot) self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot) self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot) self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot) - self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down) def clear(self): @@ -4870,145 +4653,6 @@ class FlatCAMExcEditor(QtCore.QObject): # Update cursor self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20) - - def on_canvas_key(self, event): - """ - event.key has the key. - - :param event: - :return: - """ - self.key = event.key.name - self.modifiers = QtWidgets.QApplication.keyboardModifiers() - - if self.modifiers == Qt.ControlModifier: - # save (update) the current geometry and return to the App - if self.key == 'S': - self.app.editor2object() - return - - # toggle the measurement tool - if self.key == 'M': - self.app.measurement_tool.run() - return - - # Abort the current action - if event.key.name == 'Escape': - # TODO: ...? - # self.on_tool_select("select") - self.app.inform.emit("[WARNING_NOTCL]Cancelled.") - - self.delete_utility_geometry() - - self.replot() - # self.select_btn.setChecked(True) - # self.on_tool_select('select') - self.select_tool('select') - return - - # Delete selected object - if event.key.name == 'Delete': - self.launched_from_shortcuts = True - if self.selected: - self.delete_selected() - self.replot() - else: - self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.") - return - - if event.key == '1': - self.launched_from_shortcuts = True - self.app.on_zoom_fit(None) - - if event.key == '2': - self.launched_from_shortcuts = True - self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - - if event.key == '3': - self.launched_from_shortcuts = True - self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - - # Add Array of Drill Hole Tool - if event.key.name == 'A': - self.launched_from_shortcuts = True - self.app.inform.emit("Click on target point.") - self.app.ui.add_drill_array_btn.setChecked(True) - self.select_tool('add_array') - return - - # Copy - if event.key.name == 'C': - self.launched_from_shortcuts = True - if self.selected: - self.app.inform.emit("Click on target point.") - self.app.ui.copy_drill_btn.setChecked(True) - self.on_tool_select('copy') - self.active_tool.set_origin((self.snap_x, self.snap_y)) - else: - self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to copy.") - return - - # Add Drill Hole Tool - if event.key.name == 'D': - self.launched_from_shortcuts = True - self.app.inform.emit("Click on target point.") - self.app.ui.add_drill_btn.setChecked(True) - self.select_tool('add') - return - - # Grid Snap - if event.key.name == 'G': - self.launched_from_shortcuts = True - # make sure that the cursor shape is enabled/disabled, too - if self.options['grid_snap'] is True: - self.app.app_cursor.enabled = False - else: - self.app.app_cursor.enabled = True - self.app.ui.grid_snap_btn.trigger() - return - - # Corner Snap - if event.key.name == 'K': - self.launched_from_shortcuts = True - self.app.ui.corner_snap_btn.trigger() - return - - # Move - if event.key.name == 'M': - self.launched_from_shortcuts = True - if self.selected: - self.app.inform.emit("Click on target point.") - self.app.ui.move_drill_btn.setChecked(True) - self.on_tool_select('move') - self.active_tool.set_origin((self.snap_x, self.snap_y)) - else: - self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to move.") - return - - # Resize Tool - if event.key.name == 'R': - self.launched_from_shortcuts = True - self.select_tool('resize') - return - - # Select Tool - if event.key.name == 'S': - self.launched_from_shortcuts = True - self.select_tool('select') - return - - # Propagate to tool - response = None - if self.active_tool is not None: - response = self.active_tool.on_key(event.key) - if response is not None: - self.app.inform.emit(response) - - # Show Shortcut list - if event.key.name == '`': - self.app.on_shortcut_list() - return - def on_canvas_key_release(self, event): self.key = None diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 67ba1ba4..42603d53 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -12,6 +12,8 @@ from GUIElements import * import platform import webbrowser +from FlatCAMEditor import FCShapeTool + class FlatCAMGUI(QtWidgets.QMainWindow): # Emitted when persistent window geometry needs to be retained @@ -1522,266 +1524,593 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # events from Vispy are of type KeyEvent else: key = event.key + if self.app.call_source == 'app': + if modifiers == QtCore.Qt.ControlModifier: + if key == QtCore.Qt.Key_A: + self.app.on_selectall() - if modifiers == QtCore.Qt.ControlModifier: - if key == QtCore.Qt.Key_A: - self.app.on_selectall() + if key == QtCore.Qt.Key_C: + self.app.on_copy_object() - if key == QtCore.Qt.Key_C: - self.app.on_copy_object() + if key == QtCore.Qt.Key_E: + self.app.on_fileopenexcellon() - if key == QtCore.Qt.Key_E: - self.app.on_fileopenexcellon() + if key == QtCore.Qt.Key_G: + self.app.on_fileopengerber() - if key == QtCore.Qt.Key_G: - self.app.on_fileopengerber() + if key == QtCore.Qt.Key_N: + self.app.on_file_new_click() - if key == QtCore.Qt.Key_N: - self.app.on_file_new_click() + if key == QtCore.Qt.Key_M: + self.app.measurement_tool.run() - if key == QtCore.Qt.Key_M: - self.app.measurement_tool.run() + if key == QtCore.Qt.Key_O: + self.app.on_file_openproject() - if key == QtCore.Qt.Key_O: - self.app.on_file_openproject() + if key == QtCore.Qt.Key_S: + self.app.on_file_saveproject() - if key == QtCore.Qt.Key_S: - self.app.on_file_saveproject() + # Toggle Plot Area + if key == QtCore.Qt.Key_F10 or key == 'F10': + self.app.on_toggle_plotarea() - # Toggle Plot Area - if key == QtCore.Qt.Key_F10 or key == 'F10': - self.app.on_toggle_plotarea() - - return - elif modifiers == QtCore.Qt.ShiftModifier: - - # Copy Object Name - # Copy Object Name - if key == QtCore.Qt.Key_C: - self.app.on_copy_name() - - # Toggle axis - if key == QtCore.Qt.Key_G: - if self.app.toggle_axis is False: - self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) - self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) - self.app.plotcanvas.redraw() - self.app.toggle_axis = True - else: - self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) - - self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) - self.app.plotcanvas.redraw() - self.app.toggle_axis = False - - # Open Preferences Window - if key == QtCore.Qt.Key_P: - self.app.on_preferences() return + elif modifiers == QtCore.Qt.ShiftModifier: + + # Copy Object Name + # Copy Object Name + if key == QtCore.Qt.Key_C: + self.app.on_copy_name() + + # Toggle axis + if key == QtCore.Qt.Key_G: + if self.app.toggle_axis is False: + self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) + self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0)) + self.app.plotcanvas.redraw() + self.app.toggle_axis = True + else: + self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) + + self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0)) + self.app.plotcanvas.redraw() + self.app.toggle_axis = False + + # Open Preferences Window + if key == QtCore.Qt.Key_P: + self.app.on_preferences() + return + + # Rotate Object by 90 degree CCW + if key == QtCore.Qt.Key_R: + self.app.on_rotate(silent=True, preset=-90) + return + + # Run a Script + if key == QtCore.Qt.Key_S: + self.app.on_filerunscript() + return + + # Toggle Workspace + if key == QtCore.Qt.Key_W: + self.app.on_workspace_menu() + return + + # Skew on X axis + if key == QtCore.Qt.Key_X: + self.app.on_skewx() + return + + # Skew on Y axis + if key == QtCore.Qt.Key_Y: + self.app.on_skewy() + return + + elif modifiers == QtCore.Qt.AltModifier: + # Eanble all plots + if key == Qt.Key_1: + self.app.enable_all_plots() + + # Disable all plots + if key == Qt.Key_2: + self.app.disable_all_plots() + + # Disable all other plots + if key == Qt.Key_3: + self.app.disable_other_plots() + + # Calculator Tool + if key == QtCore.Qt.Key_C: + self.app.calculator_tool.run() + + # 2-Sided PCB Tool + if key == QtCore.Qt.Key_D: + self.app.dblsidedtool.run() + return + + # Film Tool + if key == QtCore.Qt.Key_L: + self.app.film_tool.run() + return + + # Non-Copper Clear Tool + if key == QtCore.Qt.Key_N: + self.app.ncclear_tool.run() + return + + # Paint Tool + if key == QtCore.Qt.Key_P: + self.app.paint_tool.run() + return + + # Transformation Tool + if key == QtCore.Qt.Key_R: + self.app.transform_tool.run() + return + + # Cutout Tool + if key == QtCore.Qt.Key_U: + self.app.cutout_tool.run() + return + + # Panelize Tool + if key == QtCore.Qt.Key_Z: + self.app.panelize_tool.run() + return + + # Toggle Fullscreen + if key == QtCore.Qt.Key_F10 or key == 'F10': + self.app.on_fullscreen() + return + else: + # Open Manual + if key == QtCore.Qt.Key_F1 or key == 'F1': + webbrowser.open(self.app.manual_url) + + # Open Video Help + if key == QtCore.Qt.Key_F2 or key == 'F2': + webbrowser.open(self.app.video_url) + + # Show shortcut list + if key == QtCore.Qt.Key_F3 or key == 'F3': + self.app.on_shortcut_list() + + # Switch to Project Tab + if key == QtCore.Qt.Key_1: + self.app.on_select_tab('project') + + # Switch to Selected Tab + if key == QtCore.Qt.Key_2: + self.app.on_select_tab('selected') + + # Switch to Tool Tab + if key == QtCore.Qt.Key_3: + self.app.on_select_tab('tool') + + # Delete + if key == QtCore.Qt.Key_Delete or key == 'Delete': + if active: + # Delete via the application to + # ensure cleanup of the GUI + active.app.on_delete() + + # Escape = Deselect All + if key == QtCore.Qt.Key_Escape or key == 'Escape': + self.app.on_deselect_all() + self.app.inform.emit("") + + # Space = Toggle Active/Inactive + if key == QtCore.Qt.Key_Space: + for select in selected: + select.ui.plot_cb.toggle() + self.app.delete_selection_shape() + + # Copy Object Name + if key == QtCore.Qt.Key_E: + self.app.object2editor() + + # Grid toggle + if key == QtCore.Qt.Key_G: + self.app.ui.grid_snap_btn.trigger() + + # Jump to coords + if key == QtCore.Qt.Key_J: + self.app.on_jump_to() + + # New Excellon + if key == QtCore.Qt.Key_L: + self.app.new_excellon_object() + + # Move tool toggle + if key == QtCore.Qt.Key_M: + self.app.move_tool.toggle() + + # New Geometry + if key == QtCore.Qt.Key_N: + self.app.on_new_geometry() + + # Set Origin + if key == QtCore.Qt.Key_O: + self.app.on_set_origin() + return + + # Set Origin + if key == QtCore.Qt.Key_P: + self.app.properties_tool.run() + return + + # Change Units + if key == QtCore.Qt.Key_Q: + if self.app.options["units"] == 'MM': + self.app.general_options_form.general_app_group.units_radio.set_value("IN") + else: + self.app.general_options_form.general_app_group.units_radio.set_value("MM") + self.app.on_toggle_units() + + # Rotate Object by 90 degree CW + if key == QtCore.Qt.Key_R: + self.app.on_rotate(silent=True, preset=90) + + # Shell toggle + if key == QtCore.Qt.Key_S: + self.app.on_toggle_shell() + + # Transform Tool + if key == QtCore.Qt.Key_T: + self.app.transform_tool.run() + + # Zoom Fit + if key == QtCore.Qt.Key_V: + self.app.on_zoom_fit(None) + + # Mirror on X the selected object(s) + if key == QtCore.Qt.Key_X: + self.app.on_flipx() + + # Mirror on Y the selected object(s) + if key == QtCore.Qt.Key_Y: + self.app.on_flipy() + + # Zoom In + if key == QtCore.Qt.Key_Equal: + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse) + + # Zoom Out + if key == QtCore.Qt.Key_Minus: + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) + + # toggle display of Notebook area + if key == QtCore.Qt.Key_QuoteLeft: + self.app.on_toggle_notebook() - # Rotate Object by 90 degree CCW - if key == QtCore.Qt.Key_R: - self.app.on_rotate(silent=True, preset=-90) return + elif self.app.call_source == 'geo_editor': + if modifiers == QtCore.Qt.ControlModifier: + # save (update) the current geometry and return to the App + if key == QtCore.Qt.Key_S or key == 'S': + self.app.editor2object() + return - # Run a Script - if key == QtCore.Qt.Key_S: - self.app.on_filerunscript() - return + # toggle the measurement tool + if key == QtCore.Qt.Key_M or key == 'M': + self.app.measurement_tool.run() + return - # Toggle Workspace - if key == QtCore.Qt.Key_W: - self.app.on_workspace_menu() - return + elif modifiers == QtCore.Qt.ShiftModifier: + pass + elif modifiers == QtCore.Qt.AltModifier: + pass + else: + # Finish the current action. Use with tools that do not + # complete automatically, like a polygon or path. + if key == QtCore.Qt.Key_Enter or key == 'Enter': + if isinstance(self.app.geo_editor.active_tool, FCShapeTool): + self.app.geo_editor.active_tool.click( + self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y)) + self.app.geo_editor.active_tool.make() + if self.app.geo_editor.active_tool.complete: + self.app.geo_editor.on_shape_complete() + self.app.inform.emit("[success]Done.") + # automatically make the selection tool active after completing current action + self.app.geo_editor.select_tool('select') + return - # Skew on X axis - if key == QtCore.Qt.Key_X: - self.app.on_skewx() - return + # Abort the current action + if key == QtCore.Qt.Key_Escape or key == 'Escape': + # TODO: ...? + # self.on_tool_select("select") + self.app.inform.emit("[WARNING_NOTCL]Cancelled.") - # Skew on Y axis - if key == QtCore.Qt.Key_Y: - self.app.on_skewy() - return + self.app.geo_editor.delete_utility_geometry() + self.app.geo_editor.replot() + # self.select_btn.setChecked(True) + # self.on_tool_select('select') + self.app.geo_editor.select_tool('select') + return - elif modifiers == QtCore.Qt.AltModifier: - # Eanble all plots - if key == Qt.Key_1: - self.app.enable_all_plots() + # Delete selected object + if key == QtCore.Qt.Key_Delete or key == 'Delete': + self.app.geo_editor.delete_selected() + self.app.geo_editor.replot() - # Disable all plots - if key == Qt.Key_2: - self.app.disable_all_plots() + # Move + if key == QtCore.Qt.Key_Space or key == 'Space': + self.app.ui.geo_rotate_btn.setChecked(True) + self.app.geo_editor.on_tool_select('rotate') + self.app.geo_editor.active_tool.set_origin( + self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y)) - # Disable all other plots - if key == Qt.Key_3: - self.app.disable_other_plots() + if key == QtCore.Qt.Key_1 or key== '1': + self.app.on_zoom_fit(None) - # Calculator Tool - if key == QtCore.Qt.Key_C: - self.app.calculator_tool.run() + if key == QtCore.Qt.Key_2 or key == '2': + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - # 2-Sided PCB Tool - if key == QtCore.Qt.Key_D: - self.app.dblsidedtool.run() - return + if key == QtCore.Qt.Key_3 or key == '3': + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - # Film Tool - if key == QtCore.Qt.Key_L: - self.app.film_tool.run() - return + # Arc Tool + if key == QtCore.Qt.Key_A or key == 'A': + self.app.geo_editor.select_tool('arc') - # Non-Copper Clear Tool - if key == QtCore.Qt.Key_N: - self.app.ncclear_tool.run() - return + # Buffer + if key == QtCore.Qt.Key_B or key == 'B': + self.app.geo_editor.select_tool('buffer') - # Paint Tool - if key == QtCore.Qt.Key_P: - self.app.paint_tool.run() - return + # Copy + if key == QtCore.Qt.Key_C or key == 'C': + self.app.ui.geo_copy_btn.setChecked(True) + self.app.geo_editor.on_tool_select('copy') + self.app.geo_editor.active_tool.set_origin(self.snap(self.x, self.y)) + self.app.inform.emit("Click on target point.") - # Transformation Tool - if key == QtCore.Qt.Key_R: - self.app.transform_tool.run() - return + # Substract Tool + if key == QtCore.Qt.Key_E or key == 'E': + if self.app.geo_editor.get_selected() is not None: + self.app.geo_editor.intersection() + else: + msg = "Please select geometry items \n" \ + "on which to perform Intersection Tool." - # Cutout Tool - if key == QtCore.Qt.Key_U: - self.app.cutout_tool.run() - return + messagebox = QtWidgets.QMessageBox() + messagebox.setText(msg) + messagebox.setWindowTitle("Warning") + messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) + messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) + messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) + messagebox.exec_() - # Panelize Tool - if key == QtCore.Qt.Key_Z: - self.app.panelize_tool.run() - return + # Grid Snap + if key == QtCore.Qt.Key_G or key == 'G': + self.app.ui.grid_snap_btn.trigger() - # Toggle Fullscreen - if key == QtCore.Qt.Key_F10 or key == 'F10': - self.app.on_fullscreen() - return - else: - # Open Manual - if key == QtCore.Qt.Key_F1 or key == 'F1': - webbrowser.open(self.app.manual_url) + # make sure that the cursor shape is enabled/disabled, too + if self.app.geo_editor.options['grid_snap'] is True: + self.app.app_cursor.enabled = True + else: + self.app.app_cursor.enabled = False - # Open Video Help - if key == QtCore.Qt.Key_F2 or key == 'F2': - webbrowser.open(self.app.video_url) + # Paint + if key == QtCore.Qt.Key_I or key == 'I': + self.app.geo_editor.select_tool('paint') - # Show shortcut list - if key == QtCore.Qt.Key_F3 or key == 'F3': - self.app.on_shortcut_list() + # Corner Snap + if key == QtCore.Qt.Key_K or key == 'K': + self.app.geo_editor.on_corner_snap() - # Switch to Project Tab - if key == QtCore.Qt.Key_1: - self.app.on_select_tab('project') + # Move + if key == QtCore.Qt.Key_M or key == 'M': + self.app.geo_editor.on_move_click() - # Switch to Selected Tab - if key == QtCore.Qt.Key_2: - self.app.on_select_tab('selected') + # Polygon Tool + if key == QtCore.Qt.Key_N or key == 'N': + self.app.geo_editor.select_tool('polygon') - # Switch to Tool Tab - if key == QtCore.Qt.Key_3: - self.app.on_select_tab('tool') + # Circle Tool + if key == QtCore.Qt.Key_O or key == 'O': + self.app.geo_editor.select_tool('circle') - # Delete - if key == QtCore.Qt.Key_Delete or key == 'Delete': - if active: - # Delete via the application to - # ensure cleanup of the GUI - active.app.on_delete() + # Path Tool + if key == QtCore.Qt.Key_P or key == 'P': + self.app.geo_editor.select_tool('path') - # Escape = Deselect All - if key == QtCore.Qt.Key_Escape or key == 'Escape': - self.app.on_deselect_all() - self.app.inform.emit("") + # Rectangle Tool + if key == QtCore.Qt.Key_R or key == 'R': + self.app.geo_editor.select_tool('rectangle') - # Space = Toggle Active/Inactive - if key == QtCore.Qt.Key_Space: - for select in selected: - select.ui.plot_cb.toggle() - self.app.delete_selection_shape() + # Substract Tool + if key == QtCore.Qt.Key_S or key == 'S': + if self.app.geo_editor.get_selected() is not None: + self.app.geo_editor.subtract() + else: + msg = "Please select geometry items \n" \ + "on which to perform Substraction Tool." - # Copy Object Name - if key == QtCore.Qt.Key_E: - self.app.object2editor() + messagebox = QtWidgets.QMessageBox() + messagebox.setText(msg) + messagebox.setWindowTitle("Warning") + messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) + messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) + messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) + messagebox.exec_() - # Grid toggle - if key == QtCore.Qt.Key_G: - self.app.ui.grid_snap_btn.trigger() + # Add Text Tool + if key == QtCore.Qt.Key_T or key == 'T': + self.app.geo_editor.select_tool('text') - # Jump to coords - if key == QtCore.Qt.Key_J: - self.app.on_jump_to() + # Substract Tool + if key == QtCore.Qt.Key_U or key == 'U': + if self.app.geo_editor.get_selected() is not None: + self.app.geo_editor.union() + else: + msg = "Please select geometry items \n" \ + "on which to perform union." - # New Excellon - if key == QtCore.Qt.Key_L: - self.app.new_excellon_object() + messagebox = QtWidgets.QMessageBox() + messagebox.setText(msg) + messagebox.setWindowTitle("Warning") + messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) + messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) + messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) + messagebox.exec_() - # Move tool toggle - if key == QtCore.Qt.Key_M: - self.app.move_tool.toggle() + # Cut Action Tool + if key == QtCore.Qt.Key_X or key == 'X': + if self.app.geo_editor.get_selected() is not None: + self.app.geo_editor.cutpath() + else: + msg = 'Please first select a geometry item to be cutted\n' \ + 'then select the geometry item that will be cutted\n' \ + 'out of the first item. In the end press ~X~ key or\n' \ + 'the toolbar button.' \ - # New Geometry - if key == QtCore.Qt.Key_N: - self.app.on_new_geometry() + messagebox = QtWidgets.QMessageBox() + messagebox.setText(msg) + messagebox.setWindowTitle("Warning") + messagebox.setWindowIcon(QtGui.QIcon('share/warning.png')) + messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok) + messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) + messagebox.exec_() - # Set Origin - if key == QtCore.Qt.Key_O: - self.app.on_set_origin() - return + # Propagate to tool + response = None + if self.app.geo_editor.active_tool is not None: + response = self.app.geo_editor.active_tool.on_key(event.key) + if response is not None: + self.app.inform.emit(response) - # Set Origin - if key == QtCore.Qt.Key_P: - self.app.properties_tool.run() - return + # Show Shortcut list + if key == 'F3': + self.app.on_shortcut_list() - # Change Units - if key == QtCore.Qt.Key_Q: - if self.app.options["units"] == 'MM': - self.app.general_options_form.general_app_group.units_radio.set_value("IN") - else: - self.app.general_options_form.general_app_group.units_radio.set_value("MM") - self.app.on_toggle_units() + elif self.app.call_source == 'exc_editor': + if modifiers == QtCore.Qt.ControlModifier: + # save (update) the current geometry and return to the App + if key == QtCore.Qt.Key_S or key == 'S': + self.app.editor2object() + return - # Rotate Object by 90 degree CW - if key == QtCore.Qt.Key_R: - self.app.on_rotate(silent=True, preset=90) + # toggle the measurement tool + if key == QtCore.Qt.Key_M or key == 'M': + self.app.measurement_tool.run() + return - # Shell toggle - if key == QtCore.Qt.Key_S: - self.app.on_toggle_shell() + elif modifiers == QtCore.Qt.ShiftModifier: + pass + elif modifiers == QtCore.Qt.AltModifier: + pass + else: + # Abort the current action + if key == QtCore.Qt.Key_Escape or key == 'Escape': + # TODO: ...? + # self.on_tool_select("select") + self.app.inform.emit("[WARNING_NOTCL]Cancelled.") - # Transform Tool - if key == QtCore.Qt.Key_T: - self.app.transform_tool.run() + self.app.exc_editor.delete_utility_geometry() - # Zoom Fit - if key == QtCore.Qt.Key_V: - self.app.on_zoom_fit(None) + self.app.exc_editor.replot() + # self.select_btn.setChecked(True) + # self.on_tool_select('select') + self.app.exc_editor.select_tool('select') + return - # Mirror on X the selected object(s) - if key == QtCore.Qt.Key_X: - self.app.on_flipx() + # Delete selected object + if key == QtCore.Qt.Key_Delete or key == 'Delete': + self.app.exc_editor.launched_from_shortcuts = True + if self.app.exc_editor.selected: + self.app.exc_editor.delete_selected() + self.app.exc_editor.replot() + else: + self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.") + return - # Mirror on Y the selected object(s) - if key == QtCore.Qt.Key_Y: - self.app.on_flipy() + if key == QtCore.Qt.Key_1 or key == '1': + self.app.exc_editor.launched_from_shortcuts = True + self.app.on_zoom_fit(None) - # Zoom In - if key == QtCore.Qt.Key_Equal: - self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse) + if key == QtCore.Qt.Key_2 or key == '2': + self.app.exc_editor.launched_from_shortcuts = True + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - # Zoom Out - if key == QtCore.Qt.Key_Minus: - self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) + if key == QtCore.Qt.Key_3 or key == '3': + self.app.exc_editor.launched_from_shortcuts = True + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) - # toggle display of Notebook area - if key == QtCore.Qt.Key_QuoteLeft: - self.app.on_toggle_notebook() + # Add Array of Drill Hole Tool + if key == QtCore.Qt.Key_A or key == 'A': + self.app.exc_editor.launched_from_shortcuts = True + self.app.inform.emit("Click on target point.") + self.app.ui.add_drill_array_btn.setChecked(True) + self.app.exc_editor.select_tool('add_array') + return - return + # Copy + if key == QtCore.Qt.Key_C or key == 'C': + self.app.exc_editor.launched_from_shortcuts = True + if self.app.exc_editor.selected: + self.app.inform.emit("Click on target point.") + self.app.ui.copy_drill_btn.setChecked(True) + self.app.exc_editor.on_tool_select('copy') + self.app.exc_editor.active_tool.set_origin( + (self.app.exc_editor.snap_x, self.app.exc_editor.snap_y)) + else: + self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to copy.") + return + + # Add Drill Hole Tool + if key == QtCore.Qt.Key_D or key == 'D': + self.app.exc_editor.launched_from_shortcuts = True + self.app.inform.emit("Click on target point.") + self.app.ui.add_drill_btn.setChecked(True) + self.app.exc_editor.select_tool('add') + return + + # Grid Snap + if key == QtCore.Qt.Key_G or key == 'G': + self.app.exc_editor.launched_from_shortcuts = True + # make sure that the cursor shape is enabled/disabled, too + if self.app.exc_editor.options['grid_snap'] is True: + self.app.app_cursor.enabled = False + else: + self.app.app_cursor.enabled = True + self.app.ui.grid_snap_btn.trigger() + return + + # Corner Snap + if key == QtCore.Qt.Key_K or key == 'K': + self.app.exc_editor.launched_from_shortcuts = True + self.app.ui.corner_snap_btn.trigger() + return + + # Move + if key == QtCore.Qt.Key_M or key == 'M': + self.app.exc_editor.launched_from_shortcuts = True + if self.app.exc_editor.selected: + self.app.inform.emit("Click on target point.") + self.app.ui.move_drill_btn.setChecked(True) + self.app.exc_editor.on_tool_select('move') + self.app.exc_editor.active_tool.set_origin((self.snap_x, self.snap_y)) + else: + self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to move.") + return + + # Resize Tool + if key == QtCore.Qt.Key_R or key == 'R': + self.app.exc_editor.launched_from_shortcuts = True + self.app.exc_editor.select_tool('resize') + return + + # Propagate to tool + response = None + if self.app.exc_editor.active_tool is not None: + response = self.app.exc_editor.active_tool.on_key(event.key) + if response is not None: + self.app.inform.emit(response) + + # Show Shortcut list + if key == QtCore.Qt.Key_F3 or key == 'F3': + self.app.on_shortcut_list() + return def dragEnterEvent(self, event): if event.mimeData().hasUrls: diff --git a/README.md b/README.md index 16728a38..0bfcd5e4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ CAD program, and create G-Code for Isolation routing. - changed the menu entry Toggle Grid name to Toggle Grid Snap - fixed errors in Toggle Axis - fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View +- moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI 7.02.2019 From 8f000c0a18954793c1d683955c510f074fac474c Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 Feb 2019 00:08:54 +0200 Subject: [PATCH 22/27] - in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999 - in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter - in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected - changed the shortcut key for Transform Tool from 'T' to 'ALT+T' - fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message - added Double Validator for the Offset value so only float numbers can be entered. - in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab --- FlatCAMApp.py | 71 +++++++++++++-- FlatCAMEditor.py | 24 ++++- FlatCAMGUI.py | 103 +++++++++++++++++++--- FlatCAMObj.py | 135 +++++++++++++++++------------ FlatCAMTool.py | 3 + GUIElements.py | 11 ++- ObjectUI.py | 3 +- README.md | 7 ++ camlib.py | 101 +++++++++++++++------ flatcamTools/ToolNonCopperClear.py | 1 + share/letter_t_32.png | Bin 0 -> 204 bytes 11 files changed, 347 insertions(+), 112 deletions(-) create mode 100644 share/letter_t_32.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index bd059479..1e52ff7d 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1714,16 +1714,14 @@ class App(QtCore.QObject): try: if error: self.shell.append_error(msg + "\n") + elif warning: + self.shell.append_warning(msg + "\n") + elif success: + self.shell.append_success(msg + "\n") + elif selected: + self.shell.append_selected(msg + "\n") else: - if warning: - self.shell.append_warning(msg + "\n") - else: - if success: - self.shell.append_success(msg + "\n") - if success: - self.shell.append_selected(msg + "\n") - else: - self.shell.append_output(msg + "\n") + self.shell.append_output(msg + "\n") except AttributeError: log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg)) @@ -2298,6 +2296,7 @@ class App(QtCore.QObject): self.collection.set_all_inactive() self.collection.set_active(obj.options["name"]) + # here it is done the object plotting def worker_task(obj): with self.proc_container.new("Plotting"): if isinstance(obj, FlatCAMCNCjob): @@ -2740,6 +2739,60 @@ class App(QtCore.QObject): self.inform.emit("[success] A Geometry object was converted to SingleGeo type.") + def on_skey_tool_add(self): + ## Current application units in Upper Case + self.units = self.general_options_form.general_app_group.units_radio.get_value().upper() + + # work only if the notebook tab on focus is the Selected_Tab and only if the object is Geometry + if self.ui.notebook.currentWidget().objectName() == 'selected_tab': + if str(type(self.collection.get_active())) == "": + tool_add_popup = FCInputDialog(title="New Tool ...", + text='Enter a Tool Diameter:', + min=0.0000, max=99.9999, decimals=4) + tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png')) + + val, ok = tool_add_popup.get_value() + if ok: + self.collection.get_active().on_tool_add(dia=float(val)) + self.inform.emit( + "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) + else: + self.inform.emit( + "[WARNING_NOTCL] Adding Tool cancelled ...") + + # work only if the notebook tab on focus is the Tools_Tab + if self.ui.notebook.currentWidget().objectName() == 'tool_tab': + # and only if the tool is NCC Tool + if self.ui.tool_scroll_area.widget().objectName() == self.ncclear_tool.toolName: + tool_add_popup = FCInputDialog(title="New Tool ...", + text='Enter a Tool Diameter:', + min=0.0000, max=99.9999, decimals=4) + tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png')) + + val, ok = tool_add_popup.get_value() + if ok: + self.ncclear_tool.on_tool_add(dia=float(val)) + self.inform.emit( + "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) + else: + self.inform.emit( + "[WARNING_NOTCL] Adding Tool cancelled ...") + # and only if the tool is Paint Area Tool + if self.ui.tool_scroll_area.widget().objectName() == self.paint_tool.toolName: + tool_add_popup = FCInputDialog(title="New Tool ...", + text='Enter a Tool Diameter:', + min=0.0000, max=99.9999, decimals=4) + tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png')) + + val, ok = tool_add_popup.get_value() + if ok: + self.paint_tool.on_tool_add(dia=float(val)) + self.inform.emit( + "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) + else: + self.inform.emit( + "[WARNING_NOTCL] Adding Tool cancelled ...") + def on_options_dict_change(self, field): self.options_write_form_field(field) diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index 3271c242..6b830b73 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -3255,7 +3255,8 @@ class FlatCAMExcEditor(QtCore.QObject): grid1.addWidget(addtool_entry_lbl, 0, 0) hlay = QtWidgets.QHBoxLayout() - self.addtool_entry = LengthEntry() + self.addtool_entry = FCEntry() + self.addtool_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4)) hlay.addWidget(self.addtool_entry) self.addtool_btn = QtWidgets.QPushButton('Add Tool') @@ -3663,7 +3664,7 @@ class FlatCAMExcEditor(QtCore.QObject): if self.units == "IN": self.addtool_entry.set_value(0.039) else: - self.addtool_entry.set_value(1) + self.addtool_entry.set_value(1.00) sort_temp = [] @@ -3847,9 +3848,21 @@ class FlatCAMExcEditor(QtCore.QObject): # we reactivate the signals after the after the tool adding as we don't need to see the tool been populated self.tools_table_exc.itemChanged.connect(self.on_tool_edit) - def on_tool_add(self): + def on_tool_add(self, tooldia=None): self.is_modified = True - tool_dia = float(self.addtool_entry.get_value()) + if tooldia: + tool_dia = tooldia + else: + try: + tool_dia = float(self.addtool_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + tool_dia = float(self.addtool_entry.get_value().replace(',', '.')) + except ValueError: + self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, " + "use a number.") + return if tool_dia not in self.olddia_newdia: storage_elem = FlatCAMGeoEditor.make_storage() @@ -4156,6 +4169,9 @@ class FlatCAMExcEditor(QtCore.QObject): self.replot() + # add a first tool in the Tool Table + self.on_tool_add(tooldia=1.00) + def update_fcexcellon(self, exc_obj): """ Create a new Excellon object that contain the edited content of the source Excellon object diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 42603d53..848d883b 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -563,6 +563,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ### Project ### self.project_tab = QtWidgets.QWidget() + self.project_tab.setObjectName("project_tab") # project_tab.setMinimumWidth(250) # Hack self.project_tab_layout = QtWidgets.QVBoxLayout(self.project_tab) self.project_tab_layout.setContentsMargins(2, 2, 2, 2) @@ -570,6 +571,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ### Selected ### self.selected_tab = QtWidgets.QWidget() + self.selected_tab.setObjectName("selected_tab") self.selected_tab_layout = QtWidgets.QVBoxLayout(self.selected_tab) self.selected_tab_layout.setContentsMargins(2, 2, 2, 2) self.selected_scroll_area = VerticalScrollArea() @@ -578,6 +580,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ### Tool ### self.tool_tab = QtWidgets.QWidget() + self.tool_tab.setObjectName("tool_tab") self.tool_tab_layout = QtWidgets.QVBoxLayout(self.tool_tab) self.tool_tab_layout.setContentsMargins(2, 2, 2, 2) self.notebook.addTab(self.tool_tab, "Tool") @@ -826,6 +829,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
+ + + + @@ -948,7 +955,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): - + @@ -1131,6 +1138,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): + + + + @@ -1755,9 +1766,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_S: self.app.on_toggle_shell() - # Transform Tool + # Add a Tool from shortcut if key == QtCore.Qt.Key_T: - self.app.transform_tool.run() + self.app.on_skey_tool_add() # Zoom Fit if key == QtCore.Qt.Key_V: @@ -1801,6 +1812,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): elif modifiers == QtCore.Qt.AltModifier: pass else: + # toggle display of Notebook area + if key == QtCore.Qt.Key_QuoteLeft or key == '`': + self.app.on_toggle_notebook() + # Finish the current action. Use with tools that do not # complete automatically, like a polygon or path. if key == QtCore.Qt.Key_Enter or key == 'Enter': @@ -1840,14 +1855,25 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.geo_editor.active_tool.set_origin( self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y)) - if key == QtCore.Qt.Key_1 or key== '1': - self.app.on_zoom_fit(None) + if key == QtCore.Qt.Key_Minus or key == '-': + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], + [self.app.geo_editor.snap_x, self.app.geo_editor.snap_y]) + if key == QtCore.Qt.Key_Equal or key == '=': + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], + [self.app.geo_editor.snap_x, self.app.geo_editor.snap_y]) + + # Switch to Project Tab + if key == QtCore.Qt.Key_1 or key == '1': + self.app.on_select_tab('project') + + # Switch to Selected Tab if key == QtCore.Qt.Key_2 or key == '2': - self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) + self.app.on_select_tab('selected') + # Switch to Tool Tab if key == QtCore.Qt.Key_3 or key == '3': - self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) + self.app.on_select_tab('tool') # Arc Tool if key == QtCore.Qt.Key_A or key == 'A': @@ -1954,6 +1980,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok) messagebox.exec_() + if key == QtCore.Qt.Key_V or key == 'V': + self.app.on_zoom_fit(None) + # Cut Action Tool if key == QtCore.Qt.Key_X or key == 'X': if self.app.geo_editor.get_selected() is not None: @@ -1975,7 +2004,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Propagate to tool response = None if self.app.geo_editor.active_tool is not None: - response = self.app.geo_editor.active_tool.on_key(event.key) + response = self.app.geo_editor.active_tool.on_key(event) if response is not None: self.app.inform.emit(response) @@ -2024,17 +2053,41 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.") return + if key == QtCore.Qt.Key_Minus or key == '-': + self.app.exc_editor.launched_from_shortcuts = True + self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], + [self.app.exc_editor.snap_x, self.app.exc_editor.snap_y]) + return + + if key == QtCore.Qt.Key_Equal or key == '=': + self.app.exc_editor.launched_from_shortcuts = True + self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], + [self.app.exc_editor.snap_x, self.app.exc_editor.snap_y]) + return + + # toggle display of Notebook area + if key == QtCore.Qt.Key_QuoteLeft or key == '`': + self.app.exc_editor.launched_from_shortcuts = True + self.app.on_toggle_notebook() + return + + # Switch to Project Tab if key == QtCore.Qt.Key_1 or key == '1': self.app.exc_editor.launched_from_shortcuts = True - self.app.on_zoom_fit(None) + self.app.on_select_tab('project') + return + # Switch to Selected Tab if key == QtCore.Qt.Key_2 or key == '2': self.app.exc_editor.launched_from_shortcuts = True - self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) + self.app.on_select_tab('selected') + return + # Switch to Tool Tab if key == QtCore.Qt.Key_3 or key == '3': self.app.exc_editor.launched_from_shortcuts = True - self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y]) + self.app.on_select_tab('tool') + return # Add Array of Drill Hole Tool if key == QtCore.Qt.Key_A or key == 'A': @@ -2100,10 +2153,36 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.exc_editor.select_tool('resize') return + # Add Tool + if key == QtCore.Qt.Key_T or key == 'T': + self.app.exc_editor.launched_from_shortcuts = True + ## Current application units in Upper Case + self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper() + tool_add_popup = FCInputDialog(title="New Tool ...", + text='Enter a Tool Diameter:', + min=0.0000, max=99.9999, decimals=4) + tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png')) + + val, ok = tool_add_popup.get_value() + if ok: + self.app.exc_editor.on_tool_add(tooldia=val) + self.app.inform.emit( + "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) + else: + self.app.inform.emit( + "[WARNING_NOTCL] Adding Tool cancelled ...") + return + + # Zoom Fit + if key == QtCore.Qt.Key_V or key == 'V': + self.app.exc_editor.launched_from_shortcuts = True + self.app.on_zoom_fit(None) + return + # Propagate to tool response = None if self.app.exc_editor.active_tool is not None: - response = self.app.exc_editor.active_tool.on_key(event.key) + response = self.app.exc_editor.active_tool.on_key(event) if response is not None: self.app.inform.emit(response) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 377e7f5b..ff270481 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -480,7 +480,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): def geo_init(geo_obj, app_obj): assert isinstance(geo_obj, FlatCAMGeometry) - bounding_box = self.solid_geometry.envelope.buffer(self.options["noncoppermargin"]) + bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"])) if not self.options["noncopperrounded"]: bounding_box = bounding_box.envelope non_copper = bounding_box.difference(self.solid_geometry) @@ -497,7 +497,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): def geo_init(geo_obj, app_obj): assert isinstance(geo_obj, FlatCAMGeometry) # Bounding box with rounded corners - bounding_box = self.solid_geometry.envelope.buffer(self.options["bboxmargin"]) + bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"])) if not self.options["bboxrounded"]: # Remove rounded corners bounding_box = bounding_box.envelope geo_obj.solid_geometry = bounding_box @@ -557,7 +557,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): def follow_init(follow_obj, app): # Propagate options - follow_obj.options["cnctooldia"] = self.options["isotooldia"] + follow_obj.options["cnctooldia"] = float(self.options["isotooldia"]) follow_obj.solid_geometry = self.solid_geometry # TODO: Do something if this is None. Offer changing name? @@ -579,11 +579,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber): :return: None """ if dia is None: - dia = self.options["isotooldia"] + dia = float(self.options["isotooldia"]) if passes is None: passes = int(self.options["isopasses"]) if overlap is None: - overlap = self.options["isooverlap"] + overlap = float(self.options["isooverlap"]) if combine is None: combine = self.options["combine_passes"] else: @@ -638,7 +638,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): # TODO: This is ugly. Create way to pass data into init function. def iso_init(geo_obj, app_obj): # Propagate options - geo_obj.options["cnctooldia"] = self.options["isotooldia"] + geo_obj.options["cnctooldia"] = float(self.options["isotooldia"]) geo_obj.solid_geometry = [] for i in range(passes): iso_offset = (((2 * i + 1) / 2.0) * dia) - (i * overlap * dia) @@ -693,7 +693,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): # TODO: This is ugly. Create way to pass data into init function. def iso_init(geo_obj, app_obj): # Propagate options - geo_obj.options["cnctooldia"] = self.options["isotooldia"] + geo_obj.options["cnctooldia"] = float(self.options["isotooldia"]) # if milling type is climb then the move is counter-clockwise around features if milling_type == 'cl': @@ -753,8 +753,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber): factor = Gerber.convert_units(self, units) - self.options['isotooldia'] *= factor - self.options['bboxmargin'] *= factor + self.options['isotooldia'] = float(self.options['isotooldia']) * factor + self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor def plot(self, **kwargs): """ @@ -1458,7 +1458,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): outname = self.options["name"] + "_mill" if tooldia is None: - tooldia = self.options["tooldia"] + tooldia = float(self.options["tooldia"]) # Sort tools by diameter. items() -> [('name', diameter), ...] # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 @@ -1545,7 +1545,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): outname = self.options["name"] + "_mill" if tooldia is None: - tooldia = self.options["slot_tooldia"] + tooldia = float(self.options["slot_tooldia"]) # Sort tools by diameter. items() -> [('name', diameter), ...] # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 @@ -1693,13 +1693,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): job_obj.options['ppname_e'] = pp_excellon_name app_obj.progress.emit(20) - job_obj.z_cut = self.options["drillz"] - job_obj.z_move = self.options["travelz"] - job_obj.feedrate = self.options["feedrate"] - job_obj.feedrate_rapid = self.options["feedrate_rapid"] - job_obj.spindlespeed = self.options["spindlespeed"] + job_obj.z_cut = float(self.options["drillz"]) + job_obj.z_move = float(self.options["travelz"]) + job_obj.feedrate = float(self.options["feedrate"]) + job_obj.feedrate_rapid = float(self.options["feedrate_rapid"]) + job_obj.spindlespeed = float(self.options["spindlespeed"]) job_obj.dwell = self.options["dwell"] - job_obj.dwelltime = self.options["dwelltime"] + job_obj.dwelltime = float(self.options["dwelltime"]) job_obj.pp_excellon_name = pp_excellon_name job_obj.toolchange_xy_type = "excellon" @@ -1738,12 +1738,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): tools_csv = ','.join(tools) ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, - drillz=self.options['drillz'], - toolchange=self.options["toolchange"], + drillz=float(self.options['drillz']), + toolchange=float(self.options["toolchange"]), toolchangexy=self.app.defaults["excellon_toolchangexy"], - toolchangez=self.options["toolchangez"], - startz=self.options["startz"], - endz=self.options["endz"], + toolchangez=float(self.options["toolchangez"]), + startz=float(self.options["startz"]) if + self.options["startz"] else None, + endz=float(self.options["endz"]), excellon_optimization_type=self.app.defaults[ "excellon_optimization_type"]) if ret_val == 'fail': @@ -1783,11 +1784,11 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): def convert_units(self, units): factor = Excellon.convert_units(self, units) - self.options['drillz'] *= factor - self.options['travelz'] *= factor - self.options['feedrate'] *= factor - self.options['feedrate_rapid'] *= factor - self.options['toolchangez'] *= factor + self.options['drillz'] = float(self.options['drillz']) * factor + self.options['travelz'] = float(self.options['travelz']) * factor + self.options['feedrate'] = float(self.options['feedrate']) * factor + self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor + self.options['toolchangez'] = float(self.options['toolchangez']) * factor if self.app.defaults["excellon_toolchangexy"] == '': self.options['toolchangexy'] = "0.0, 0.0" @@ -1802,8 +1803,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) if self.options['startz'] is not None: - self.options['startz'] *= factor - self.options['endz'] *= factor + self.options['startz'] = float(self.options['startz']) * factor + self.options['endz'] = float(self.options['endz']) * factor def plot(self): @@ -2271,7 +2272,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): if not self.tools: self.tools.update({ self.tooluid: { - 'tooldia': self.options["cnctooldia"], + 'tooldia': float(self.options["cnctooldia"]), 'offset': 'Path', 'offset_value': 0.0, 'type': 'Rough', @@ -2816,8 +2817,28 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): self.ui.cutz_entry.setDisabled(False) def update_cutz(self): - vdia = float(self.ui.tipdia_entry.get_value()) - half_vangle = float(self.ui.tipangle_entry.get_value()) / 2 + try: + vdia = float(self.ui.tipdia_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + vdia = float(self.ui.tipdia_entry.get_value().replace(',', '.')) + except ValueError: + self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, " + "use a number.") + return + + try: + half_vangle = float(self.ui.tipangle_entry.get_value()) / 2 + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + half_vangle = float(self.ui.tipangle_entry.get_value().replace(',', '.')) / 2 + except ValueError: + self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, " + "use a number.") + return + row = self.ui.geo_tools_table.currentRow() tool_uid = int(self.ui.geo_tools_table.item(row, 5).text()) @@ -3615,36 +3636,36 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): :return: None """ - tooldia = tooldia if tooldia else self.options["cnctooldia"] - outname = outname if outname is not None else self.options["name"] + tooldia = tooldia if tooldia else float(self.options["cnctooldia"]) + outname = outname if outname is not None else float(self.options["name"]) - z_cut = z_cut if z_cut is not None else self.options["cutz"] - z_move = z_move if z_move is not None else self.options["travelz"] + z_cut = z_cut if z_cut is not None else float(self.options["cutz"]) + z_move = z_move if z_move is not None else float(self.options["travelz"]) - feedrate = feedrate if feedrate is not None else self.options["feedrate"] - feedrate_z = feedrate_z if feedrate_z is not None else self.options["feedrate_z"] - feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else self.options["feedrate_rapid"] + feedrate = feedrate if feedrate is not None else float(self.options["feedrate"]) + feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"]) + feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"]) multidepth = multidepth if multidepth is not None else self.options["multidepth"] - depthperpass = depthperpass if depthperpass is not None else self.options["depthperpass"] + depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"]) segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) - extracut = extracut if extracut is not None else self.options["extracut"] - startz = startz if startz is not None else self.options["startz"] - endz = endz if endz is not None else self.options["endz"] + extracut = extracut if extracut is not None else float(self.options["extracut"]) + startz = startz if startz is not None else float(self.options["startz"]) + endz = endz if endz is not None else float(self.options["endz"]) - toolchangez = toolchangez if toolchangez else self.options["toolchangez"] + toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"]) toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"] toolchange = toolchange if toolchange else self.options["toolchange"] offset = offset if offset else 0.0 # int or None. - spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed'] + spindlespeed = spindlespeed if spindlespeed else int(self.options['spindlespeed']) dwell = dwell if dwell else self.options["dwell"] - dwelltime = dwelltime if dwelltime else self.options["dwelltime"] + dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"]) ppname_g = ppname_g if ppname_g else self.options["ppname_g"] @@ -3827,19 +3848,19 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): factor = Geometry.convert_units(self, units) - self.options['cutz'] *= factor - self.options['depthperpass'] *= factor - self.options['travelz'] *= factor - self.options['feedrate'] *= factor - self.options['feedrate_z'] *= factor - self.options['feedrate_rapid'] *= factor - self.options['endz'] *= factor + self.options['cutz'] = float(self.options['cutz']) * factor + self.options['depthperpass'] = float(self.options['depthperpass']) * factor + self.options['travelz'] = float(self.options['travelz']) * factor + self.options['feedrate'] = float(self.options['feedrate']) * factor + self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor + self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor + self.options['endz'] = float(self.options['endz']) * factor # self.options['cnctooldia'] *= factor # self.options['painttooldia'] *= factor # self.options['paintmargin'] *= factor # self.options['paintoverlap'] *= factor - self.options["toolchangez"] *= factor + self.options["toolchangez"] = float(self.options["toolchangez"]) * factor if self.app.defaults["geometry_toolchangexy"] == '': self.options['toolchangexy'] = "0.0, 0.0" @@ -3854,7 +3875,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) if self.options['startz'] is not None: - self.options['startz'] *= factor + self.options['startz'] = float(self.options['startz']) * factor param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', 'endz', 'toolchangez'] @@ -4582,7 +4603,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): try: if self.multitool is False: # single tool usage - self.plot2(tooldia=self.options["tooldia"], obj=self, visible=visible, kind=kind) + self.plot2(tooldia=float(self.options["tooldia"]), obj=self, visible=visible, kind=kind) else: # multiple tools usage for tooluid_key in self.cnc_tools: @@ -4597,7 +4618,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): def convert_units(self, units): factor = CNCjob.convert_units(self, units) FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_units()") - self.options["tooldia"] *= factor + self.options["tooldia"] = float(self.options["tooldia"]) * factor param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', 'endz', 'toolchangez'] diff --git a/FlatCAMTool.py b/FlatCAMTool.py index 585f9fa1..0ddeefcf 100644 --- a/FlatCAMTool.py +++ b/FlatCAMTool.py @@ -83,6 +83,9 @@ class FlatCAMTool(QtWidgets.QWidget): # Put ourself in the GUI self.app.ui.tool_scroll_area.setWidget(self) + # Set the tool name as the widget object name + self.app.ui.tool_scroll_area.widget().setObjectName(self.toolName) + # Switch notebook to tool page self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) diff --git a/GUIElements.py b/GUIElements.py index 54dc0c54..7f3fb6fe 100644 --- a/GUIElements.py +++ b/GUIElements.py @@ -233,7 +233,6 @@ class FloatEntry(QtWidgets.QLineEdit): else: self.setText("") - def sizeHint(self): default_hint_size = super(FloatEntry, self).sizeHint() return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height()) @@ -351,7 +350,7 @@ class FCEntry2(FCEntry): self.readyToEdit = True def set_value(self, val): - self.setText('%.5f' % float(val)) + self.setText('%.4f' % float(val)) class EvalEntry(QtWidgets.QLineEdit): @@ -474,6 +473,7 @@ class FCTextAreaRich(QtWidgets.QTextEdit): default_hint_size = super(FCTextAreaRich, self).sizeHint() return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height()) + class FCComboBox(QtWidgets.QComboBox): def __init__(self, parent=None): super(FCComboBox, self).__init__(parent) @@ -494,6 +494,10 @@ class FCInputDialog(QtWidgets.QInputDialog): super(FCInputDialog, self).__init__(parent) self.allow_empty = ok self.empty_val = val + + self.val = 0.0 + self.ok = '' + if title is None: self.title = 'title' else: @@ -515,9 +519,8 @@ class FCInputDialog(QtWidgets.QInputDialog): else: self.decimals = decimals - def get_value(self): - self.val,self.ok = self.getDouble(self, self.title, self.text, min=self.min, + self.val, self.ok = self.getDouble(self, self.title, self.text, min=self.min, max=self.max, decimals=self.decimals) return [self.val, self.ok] diff --git a/ObjectUI.py b/ObjectUI.py index 35f29727..0832a430 100644 --- a/ObjectUI.py +++ b/ObjectUI.py @@ -783,7 +783,8 @@ class GeometryObjectUI(ObjectUI): "cut and negative for 'inside' cut." ) self.grid1.addWidget(self.tool_offset_lbl, 0, 0) - self.tool_offset_entry = FCEntry() + self.tool_offset_entry = FloatEntry() + self.tool_offset_entry.setValidator(QtGui.QDoubleValidator(-9999.9999, 9999.9999, 4)) spacer_lbl = QtWidgets.QLabel(" ") spacer_lbl.setFixedWidth(80) diff --git a/README.md b/README.md index 0bfcd5e4..7e15236b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ CAD program, and create G-Code for Isolation routing. - fixed errors in Toggle Axis - fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View - moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI +- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999 +- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter +- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected +- changed the shortcut key for Transform Tool from 'T' to 'ALT+T' +- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message +- added Double Validator for the Offset value so only float numbers can be entered. +- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab 7.02.2019 diff --git a/camlib.py b/camlib.py index 83f56786..fda02701 100644 --- a/camlib.py +++ b/camlib.py @@ -4939,25 +4939,25 @@ class CNCjob(Geometry): flat_geometry = self.flatten(temp_solid_geometry, pathonly=True) log.debug("%d paths" % len(flat_geometry)) - self.tooldia = tooldia - self.z_cut = z_cut - self.z_move = z_move + self.tooldia = float(tooldia) if tooldia else None + self.z_cut = float(z_cut) if z_cut else None + self.z_move = float(z_move) if z_move else None - self.feedrate = feedrate - self.feedrate_z = feedrate_z - self.feedrate_rapid = feedrate_rapid + self.feedrate = float(feedrate) if feedrate else None + self.feedrate_z = float(feedrate_z) if feedrate_z else None + self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None - self.spindlespeed = spindlespeed + self.spindlespeed = int(spindlespeed) if spindlespeed else None self.dwell = dwell - self.dwelltime = dwelltime + self.dwelltime = float(dwelltime) if dwelltime else None - self.startz = startz - self.endz = endz + self.startz = float(startz) if startz else None + self.endz = float(endz) if endz else None - self.depthpercut = depthpercut + self.depthpercut = float(depthpercut) if depthpercut else None self.multidepth = multidepth - self.toolchangez = toolchangez + self.toolchangez = float(toolchangez) if toolchangez else None try: if toolchangexy == '': @@ -5120,14 +5120,57 @@ class CNCjob(Geometry): "from a Geometry object without solid_geometry.") temp_solid_geometry = [] + + def bounds_rec(obj): + if type(obj) is list: + minx = Inf + miny = Inf + maxx = -Inf + maxy = -Inf + + for k in obj: + if type(k) is dict: + for key in k: + minx_, miny_, maxx_, maxy_ = bounds_rec(k[key]) + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + else: + minx_, miny_, maxx_, maxy_ = bounds_rec(k) + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + return minx, miny, maxx, maxy + else: + # it's a Shapely object, return it's bounds + return obj.bounds + if offset != 0.0: + offset_for_use = offset + + if offset <0: + a, b, c, d = bounds_rec(geometry.solid_geometry) + # if the offset is less than half of the total length or less than half of the total width of the + # solid geometry it's obvious we can't do the offset + if -offset > ((c - a) / 2) or -offset > ((d - b) / 2): + self.app.inform.emit("[ERROR_NOTCL]The Tool Offset value is too negative to use " + "for the current_geometry.\n" + "Raise the value (in module) and try again.") + return 'fail' + # hack: make offset smaller by 0.0000000001 which is insignificant difference but allow the job + # to continue + elif -offset == ((c - a) / 2) or -offset == ((d - b) / 2): + offset_for_use = offset - 0.0000000001 + for it in geometry.solid_geometry: # if the geometry is a closed shape then create a Polygon out of it if isinstance(it, LineString): c = it.coords if c[0] == c[-1]: it = Polygon(it) - temp_solid_geometry.append(it.buffer(offset, join_style=2)) + temp_solid_geometry.append(it.buffer(offset_for_use, join_style=2)) else: temp_solid_geometry = geometry.solid_geometry @@ -5135,25 +5178,33 @@ class CNCjob(Geometry): flat_geometry = self.flatten(temp_solid_geometry, pathonly=True) log.debug("%d paths" % len(flat_geometry)) - self.tooldia = tooldia - self.z_cut = z_cut - self.z_move = z_move + self.tooldia = float(tooldia) if tooldia else None - self.feedrate = feedrate - self.feedrate_z = feedrate_z - self.feedrate_rapid = feedrate_rapid + self.z_cut = float(z_cut) if z_cut else None + + self.z_move = float(z_move) if z_move else None + + self.feedrate = float(feedrate) if feedrate else None + + self.feedrate_z = float(feedrate_z) if feedrate_z else None + + self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None + + self.spindlespeed = int(spindlespeed) if spindlespeed else None - self.spindlespeed = spindlespeed self.dwell = dwell - self.dwelltime = dwelltime - self.startz = startz - self.endz = endz + self.dwelltime = float(dwelltime) if dwelltime else None + + self.startz = float(startz) if startz else None + + self.endz = float(endz) if endz else None + + self.depthpercut = float(depthpercut) if depthpercut else None - self.depthpercut = depthpercut self.multidepth = multidepth - self.toolchangez = toolchangez + self.toolchangez = float(toolchangez) if toolchangez else None try: if toolchangexy == '': diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index 3768bd65..020fd314 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -5,6 +5,7 @@ from copy import copy,deepcopy from ObjectCollection import * import time + class NonCopperClear(FlatCAMTool, Gerber): toolName = "Non-Copper Clearing" diff --git a/share/letter_t_32.png b/share/letter_t_32.png new file mode 100644 index 0000000000000000000000000000000000000000..83cc9b34d5072c70822ba466b90201ca539909e9 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!V4|msV@QPi+j9qb9TEgsAL=>_M-_^RX1b`p zDiBcBXs|rx*23~XHO^@J*=OBb`nK`Uep$rS#Vj=|Z%a_!z6&?sZk!l*(?K%ea=x&F tLjwaN6AOm`kkRm{VcM?{SAOx0%!|#w&b6(0q6xH&!PC{xWt~$(698+8K@9)^ literal 0 HcmV?d00001 From da7029dc0b145efc918f5936b5941eb108e8c90f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 Feb 2019 01:18:03 +0200 Subject: [PATCH 23/27] - if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value. --- FlatCAMApp.py | 20 +++++++++++++------- FlatCAMGUI.py | 2 +- README.md | 1 + flatcamTools/ToolNonCopperClear.py | 7 +++++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 1e52ff7d..d2e5d916 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -93,7 +93,7 @@ class App(QtCore.QObject): # Version version = 8.908 - version_date = "2019/02/7" + version_date = "2019/02/9" beta = True # current date now @@ -2753,9 +2753,11 @@ class App(QtCore.QObject): val, ok = tool_add_popup.get_value() if ok: + if float(val) == 0: + self.inform.emit( + "[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.") + return self.collection.get_active().on_tool_add(dia=float(val)) - self.inform.emit( - "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) else: self.inform.emit( "[WARNING_NOTCL] Adding Tool cancelled ...") @@ -2771,9 +2773,11 @@ class App(QtCore.QObject): val, ok = tool_add_popup.get_value() if ok: + if float(val) == 0: + self.inform.emit( + "[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.") + return self.ncclear_tool.on_tool_add(dia=float(val)) - self.inform.emit( - "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) else: self.inform.emit( "[WARNING_NOTCL] Adding Tool cancelled ...") @@ -2786,9 +2790,11 @@ class App(QtCore.QObject): val, ok = tool_add_popup.get_value() if ok: + if float(val) == 0: + self.inform.emit( + "[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.") + return self.paint_tool.on_tool_add(dia=float(val)) - self.inform.emit( - "[success]Added new tool with dia: %s %s" % ('%.4f' % float(val), str(self.units))) else: self.inform.emit( "[WARNING_NOTCL] Adding Tool cancelled ...") diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 848d883b..ec23266e 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -831,7 +831,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): - + diff --git a/README.md b/README.md index 7e15236b..415e7899 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ CAD program, and create G-Code for Isolation routing. - fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message - added Double Validator for the Offset value so only float numbers can be entered. - in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab +- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value. 7.02.2019 diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index 020fd314..e8f7f041 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -447,6 +447,10 @@ class NonCopperClear(FlatCAMTool, Gerber): self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.") return + if tool_dia == 0: + self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.") + return + # construct a list of all 'tooluid' in the self.tools tool_uid_list = [] for tooluid_key in self.ncc_tools: @@ -619,7 +623,6 @@ class NonCopperClear(FlatCAMTool, Gerber): self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % self.obj_name) return "Could not retrieve object: %s" % self.obj_name - # Prepare non-copper polygons try: bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre) @@ -627,7 +630,7 @@ class NonCopperClear(FlatCAMTool, Gerber): self.app.inform.emit("[ERROR_NOTCL]No Gerber file available.") return - # calculate the empty area by substracting the solid_geometry from the object bounding box geometry + # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry empty = self.ncc_obj.get_empty_area(bounding_box) if type(empty) is Polygon: empty = MultiPolygon([empty]) From 8b0a9a429a4b9e0cbbb4b872fa1fa404fd658dce Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 Feb 2019 01:47:59 +0200 Subject: [PATCH 24/27] - fixed some errors --- FlatCAMEditor.py | 6 +++--- FlatCAMObj.py | 5 +++-- README.md | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index 6b830b73..38c333b8 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -1572,14 +1572,14 @@ class FCDrillArray(FCShapeTool): for item in range(self.drill_array_size): if self.drill_axis == 'X': - geo = self.util_shape(((data[0] + (self.drill_pitch * item)), data[1])) + geo = self.util_shape(((dx + (self.drill_pitch * item)), dy)) if self.drill_axis == 'Y': - geo = self.util_shape((data[0], (data[1] + (self.drill_pitch * item)))) + geo = self.util_shape((dx, (dy + (self.drill_pitch * item)))) if self.drill_axis == 'A': x_adj = self.drill_pitch * math.cos(math.radians(self.drill_linear_angle)) y_adj = self.drill_pitch * math.sin(math.radians(self.drill_linear_angle)) geo = self.util_shape( - ((data[0] + (x_adj * item)), (data[1] + (y_adj * item))) + ((dx + (x_adj * item)), (dy + (y_adj * item))) ) if static is None or static is False: diff --git a/FlatCAMObj.py b/FlatCAMObj.py index ff270481..9a863ebf 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1697,7 +1697,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): job_obj.z_move = float(self.options["travelz"]) job_obj.feedrate = float(self.options["feedrate"]) job_obj.feedrate_rapid = float(self.options["feedrate_rapid"]) - job_obj.spindlespeed = float(self.options["spindlespeed"]) + + job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None job_obj.dwell = self.options["dwell"] job_obj.dwelltime = float(self.options["dwelltime"]) job_obj.pp_excellon_name = pp_excellon_name @@ -4307,8 +4308,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): _filter_ = "G-Code Files (*.nc);;G-Code Files (*.txt);;G-Code Files (*.tap);;G-Code Files (*.cnc);;" \ "G-Code Files (*.g-code);;All Files (*.*)" - dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) try: + dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) filename, _ = QtWidgets.QFileDialog.getSaveFileName( caption="Export Machine Code ...", directory=dir_file_to_save, diff --git a/README.md b/README.md index 415e7899..a2d78ef4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +9.02.2019 + +- + 8.02.2019 - when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible From d17fc12196187033057f07ccb787a44c41508d33 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 Feb 2019 01:58:08 +0200 Subject: [PATCH 25/27] - added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory --- FlatCAMApp.py | 2 ++ README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index d2e5d916..a984366f 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1665,6 +1665,8 @@ class App(QtCore.QObject): loc = self.defaults["global_last_save_folder"] if loc is None: loc = self.defaults["global_last_folder"] + if loc is None: + loc = os.path.dirname(__file__) return loc def report_usage(self, resource): diff --git a/README.md b/README.md index a2d78ef4..98d63e29 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ CAD program, and create G-Code for Isolation routing. 9.02.2019 -- +- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory 8.02.2019 From 8665b1d8c3b84d67cc4ffcb4e16f65c68996a39b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 Feb 2019 11:52:08 +0200 Subject: [PATCH 26/27] - added into Preferences the Calculator Tools - made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before) - made invisible some buttons that could mislead (the ones for slots converted to drills which I deem unsafe) --- FlatCAMApp.py | 20 ++++- FlatCAMGUI.py | 130 ++++++++++++++++++++++++++++---- ObjectUI.py | 3 +- README.md | 2 + flatcamTools/ToolCalculators.py | 36 +++++---- 5 files changed, 158 insertions(+), 33 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index a984366f..deab459c 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -439,7 +439,15 @@ class App(QtCore.QObject): "tools_panelize_rows": self.tools_defaults_form.tools_panelize_group.prows, "tools_panelize_constrain": self.tools_defaults_form.tools_panelize_group.pconstrain_cb, "tools_panelize_constrainx": self.tools_defaults_form.tools_panelize_group.px_width_entry, - "tools_panelize_constrainy": self.tools_defaults_form.tools_panelize_group.py_height_entry + "tools_panelize_constrainy": self.tools_defaults_form.tools_panelize_group.py_height_entry, + + "tools_calc_vshape_tip_dia": self.tools_defaults_form.tools_calculators_group.tip_dia_entry, + "tools_calc_vshape_tip_angle": self.tools_defaults_form.tools_calculators_group.tip_angle_entry, + "tools_calc_vshape_cut_z": self.tools_defaults_form.tools_calculators_group.cut_z_entry, + "tools_calc_electro_length": self.tools_defaults_form.tools_calculators_group.pcblength_entry, + "tools_calc_electro_width": self.tools_defaults_form.tools_calculators_group.pcbwidth_entry, + "tools_calc_electro_cdensity": self.tools_defaults_form.tools_calculators_group.cdensity_entry, + "tools_calc_electro_growth": self.tools_defaults_form.tools_calculators_group.growth_entry } # loads postprocessors self.postprocessors = load_postprocessors(self) @@ -625,7 +633,15 @@ class App(QtCore.QObject): "tools_panelize_rows": 1, "tools_panelize_constrain": False, "tools_panelize_constrainx": 0.0, - "tools_panelize_constrainy": 0.0 + "tools_panelize_constrainy": 0.0, + + "tools_calc_vshape_tip_dia": 0.007874, + "tools_calc_vshape_tip_angle": 30, + "tools_calc_vshape_cut_z": 0.000787, + "tools_calc_electro_length": 10.0, + "tools_calc_electro_width": 10.0, + "tools_calc_electro_cdensity":13.0, + "tools_calc_electro_growth": 10.0 }) ############################### diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index ec23266e..9065d647 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -644,7 +644,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.options_combo.setVisible(False) self.hlay1.addStretch() - self.general_scroll_area = VerticalScrollArea() + self.general_scroll_area = QtWidgets.QScrollArea() self.general_tab_lay.addWidget(self.general_scroll_area) self.gerber_tab = QtWidgets.QWidget() @@ -653,7 +653,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.gerber_tab_lay.setContentsMargins(2, 2, 2, 2) self.gerber_tab.setLayout(self.gerber_tab_lay) - self.gerber_scroll_area = VerticalScrollArea() + self.gerber_scroll_area = QtWidgets.QScrollArea() self.gerber_tab_lay.addWidget(self.gerber_scroll_area) self.excellon_tab = QtWidgets.QWidget() @@ -662,7 +662,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.excellon_tab_lay.setContentsMargins(2, 2, 2, 2) self.excellon_tab.setLayout(self.excellon_tab_lay) - self.excellon_scroll_area = VerticalScrollArea() + self.excellon_scroll_area = QtWidgets.QScrollArea() self.excellon_tab_lay.addWidget(self.excellon_scroll_area) self.geometry_tab = QtWidgets.QWidget() @@ -671,7 +671,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.geometry_tab_lay.setContentsMargins(2, 2, 2, 2) self.geometry_tab.setLayout(self.geometry_tab_lay) - self.geometry_scroll_area = VerticalScrollArea() + self.geometry_scroll_area = QtWidgets.QScrollArea() self.geometry_tab_lay.addWidget(self.geometry_scroll_area) self.cncjob_tab = QtWidgets.QWidget() @@ -680,7 +680,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.cncjob_tab_lay.setContentsMargins(2, 2, 2, 2) self.cncjob_tab.setLayout(self.cncjob_tab_lay) - self.cncjob_scroll_area = VerticalScrollArea() + self.cncjob_scroll_area = QtWidgets.QScrollArea() self.cncjob_tab_lay.addWidget(self.cncjob_scroll_area) self.tools_tab = QtWidgets.QWidget() @@ -689,7 +689,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.tools_tab_lay.setContentsMargins(2, 2, 2, 2) self.tools_tab.setLayout(self.tools_tab_lay) - self.tools_scroll_area = VerticalScrollArea() + self.tools_scroll_area = QtWidgets.QScrollArea() self.tools_tab_lay.addWidget(self.tools_scroll_area) self.pref_tab_bottom_layout = QtWidgets.QHBoxLayout() @@ -2352,21 +2352,24 @@ class ToolsPreferencesUI(QtWidgets.QWidget): self.setLayout(self.layout) self.tools_ncc_group = ToolsNCCPrefGroupUI() - self.tools_ncc_group.setFixedWidth(200) + self.tools_ncc_group.setMinimumWidth(200) self.tools_paint_group = ToolsPaintPrefGroupUI() - self.tools_paint_group.setFixedWidth(200) + self.tools_paint_group.setMinimumWidth(200) self.tools_cutout_group = ToolsCutoutPrefGroupUI() - self.tools_cutout_group.setFixedWidth(220) + self.tools_cutout_group.setMinimumWidth(220) self.tools_2sided_group = Tools2sidedPrefGroupUI() - self.tools_2sided_group.setFixedWidth(220) + self.tools_2sided_group.setMinimumWidth(220) self.tools_film_group = ToolsFilmPrefGroupUI() - self.tools_film_group.setFixedWidth(220) + self.tools_film_group.setMinimumWidth(220) self.tools_panelize_group = ToolsPanelizePrefGroupUI() - self.tools_panelize_group.setFixedWidth(200) + self.tools_panelize_group.setMinimumWidth(220) + + self.tools_calculators_group = ToolsCalculatorsPrefGroupUI() + self.tools_calculators_group.setMinimumWidth(220) self.vlay = QtWidgets.QVBoxLayout() self.vlay.addWidget(self.tools_ncc_group) @@ -2379,6 +2382,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget): self.vlay2 = QtWidgets.QVBoxLayout() self.vlay2.addWidget(self.tools_panelize_group) + self.vlay2.addWidget(self.tools_calculators_group) self.layout.addLayout(self.vlay) self.layout.addLayout(self.vlay1) @@ -2386,6 +2390,7 @@ class ToolsPreferencesUI(QtWidgets.QWidget): self.layout.addStretch() + class CNCJobPreferencesUI(QtWidgets.QWidget): def __init__(self, parent=None): @@ -3409,8 +3414,8 @@ class ExcellonOptPrefGroupUI(OptionsGroupUI): grid2.addWidget(self.excellon_gcode_type_radio, 16, 1) # until I decide to implement this feature those remain disabled - excellon_gcode_type_label.setDisabled(True) - self.excellon_gcode_type_radio.setDisabled(True) + excellon_gcode_type_label.hide() + self.excellon_gcode_type_radio.setVisible(False) #### Milling Holes #### self.mill_hole_label = QtWidgets.QLabel('Mill Holes') @@ -4368,6 +4373,103 @@ class ToolsPanelizePrefGroupUI(OptionsGroupUI): self.layout.addStretch() +class ToolsCalculatorsPrefGroupUI(OptionsGroupUI): + def __init__(self, parent=None): + # OptionsGroupUI.__init__(self, "Calculators Tool Options", parent=parent) + super(ToolsCalculatorsPrefGroupUI, self).__init__(self) + + self.setTitle(str("Calculators Tool Options")) + + ## V-shape Calculator Tool + self.vshape_tool_label = QtWidgets.QLabel("V-Shape Tool Calculator:") + self.vshape_tool_label.setToolTip( + "Calculate the tool diameter for a given V-shape tool,\n" + "having the tip diameter, tip angle and\n" + "depth-of-cut as parameters." + ) + self.layout.addWidget(self.vshape_tool_label) + + grid0 = QtWidgets.QGridLayout() + self.layout.addLayout(grid0) + + ## Tip Diameter + self.tip_dia_entry = FCEntry() + self.tip_dia_label = QtWidgets.QLabel("Tip Diameter:") + self.tip_dia_label.setToolTip( + "This is the tool tip diameter.\n" + "It is specified by manufacturer." + ) + grid0.addWidget(self.tip_dia_label, 0, 0) + grid0.addWidget(self.tip_dia_entry, 0, 1) + + ## Tip angle + self.tip_angle_entry = FCEntry() + self.tip_angle_label = QtWidgets.QLabel("Tip angle:") + self.tip_angle_label.setToolTip( + "This is the angle on the tip of the tool.\n" + "It is specified by manufacturer." + ) + grid0.addWidget(self.tip_angle_label, 1, 0) + grid0.addWidget(self.tip_angle_entry, 1, 1) + + ## Depth-of-cut Cut Z + self.cut_z_entry = FCEntry() + self.cut_z_label = QtWidgets.QLabel("Cut Z:") + self.cut_z_label.setToolTip( + "This is depth to cut into material.\n" + "In the CNCJob object it is the CutZ parameter." + ) + grid0.addWidget(self.cut_z_label, 2, 0) + grid0.addWidget(self.cut_z_entry, 2, 1) + + ## Electroplating Calculator Tool + self.plate_title_label = QtWidgets.QLabel("ElectroPlating Calculator:") + self.plate_title_label.setToolTip( + "This calculator is useful for those who plate the via/pad/drill holes,\n" + "using a method like grahite ink or calcium hypophosphite ink or palladium chloride." + ) + self.layout.addWidget(self.plate_title_label) + + grid1 = QtWidgets.QGridLayout() + self.layout.addLayout(grid1) + + ## PCB Length + self.pcblength_entry = FCEntry() + self.pcblengthlabel = QtWidgets.QLabel("Board Length:") + + self.pcblengthlabel.setToolTip('This is the board length. In centimeters.') + grid1.addWidget(self.pcblengthlabel, 0, 0) + grid1.addWidget(self.pcblength_entry, 0, 1) + + ## PCB Width + self.pcbwidth_entry = FCEntry() + self.pcbwidthlabel = QtWidgets.QLabel("Board Width:") + + self.pcbwidthlabel.setToolTip('This is the board width.In centimeters.') + grid1.addWidget(self.pcbwidthlabel, 1, 0) + grid1.addWidget(self.pcbwidth_entry, 1, 1) + + ## Current Density + self.cdensity_label = QtWidgets.QLabel("Current Density:") + self.cdensity_entry = FCEntry() + + self.cdensity_label.setToolTip("Current density to pass through the board. \n" + "In Amps per Square Feet ASF.") + grid1.addWidget(self.cdensity_label, 2, 0) + grid1.addWidget(self.cdensity_entry, 2, 1) + + ## PCB Copper Growth + self.growth_label = QtWidgets.QLabel("Copper Growth:") + self.growth_entry = FCEntry() + + self.growth_label.setToolTip("How thick the copper growth is intended to be.\n" + "In microns.") + grid1.addWidget(self.growth_label, 3, 0) + grid1.addWidget(self.growth_entry, 3, 1) + + self.layout.addStretch() + + class FlatCAMActivityView(QtWidgets.QWidget): def __init__(self, parent=None): diff --git a/ObjectUI.py b/ObjectUI.py index 0832a430..54e97c42 100644 --- a/ObjectUI.py +++ b/ObjectUI.py @@ -611,7 +611,8 @@ class ExcellonObjectUI(ObjectUI): self.tools_box.addLayout(gcode_box) # temporary action until I finish the feature - self.excellon_gcode_type_radio.setEnabled(False) + self.excellon_gcode_type_radio.setVisible(False) + gcode_type_label.hide() self.generate_cnc_button = QtWidgets.QPushButton('Create GCode') self.generate_cnc_button.setToolTip( diff --git a/README.md b/README.md index 98d63e29..dbc03b4f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ CAD program, and create G-Code for Isolation routing. 9.02.2019 - added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory +- added into Preferences the Calculator Tools +- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before) 8.02.2019 diff --git a/flatcamTools/ToolCalculators.py b/flatcamTools/ToolCalculators.py index cd42881e..fc09ebd6 100644 --- a/flatcamTools/ToolCalculators.py +++ b/flatcamTools/ToolCalculators.py @@ -224,27 +224,31 @@ class ToolCalculator(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs) def set_tool_ui(self): + self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper() + ## Initialize form self.mm_entry.set_value('0') self.inch_entry.set_value('0') - self.pcblength_entry.set_value('10') - self.pcbwidth_entry.set_value('10') - self.cdensity_entry.set_value('13') - self.growth_entry.set_value('10') - self.cvalue_entry.set_value(2.80) - self.time_entry.set_value(33.0) + length = self.app.defaults["tools_calc_electro_length"] + width = self.app.defaults["tools_calc_electro_width"] + density = self.app.defaults["tools_calc_electro_cdensity"] + growth = self.app.defaults["tools_calc_electro_growth"] + self.pcblength_entry.set_value(length) + self.pcbwidth_entry.set_value(width) + self.cdensity_entry.set_value(density) + self.growth_entry.set_value(growth) + self.cvalue_entry.set_value(0.00) + self.time_entry.set_value(0.0) - if self.app.defaults["units"] == 'MM': - self.tipDia_entry.set_value('0.2') - self.tipAngle_entry.set_value('45') - self.cutDepth_entry.set_value('0.25') - self.effectiveToolDia_entry.set_value('0.39') - else: - self.tipDia_entry.set_value('7.87402') - self.tipAngle_entry.set_value('45') - self.cutDepth_entry.set_value('9.84252') - self.effectiveToolDia_entry.set_value('15.35433') + tip_dia = self.app.defaults["tools_calc_vshape_tip_dia"] + tip_angle = self.app.defaults["tools_calc_vshape_tip_angle"] + cut_z = self.app.defaults["tools_calc_vshape_cut_z"] + + self.tipDia_entry.set_value(tip_dia) + self.tipAngle_entry.set_value(tip_angle) + self.cutDepth_entry.set_value(cut_z) + self.effectiveToolDia_entry.set_value('0.0000') def on_calculate_tool_dia(self): # Calculation: From 59e12cf295dd581477919df62771ddf701ba4846 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 9 Feb 2019 16:00:47 +0200 Subject: [PATCH 27/27] - fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor - changed the messages from status bar on new object creation/selection - in Geometry Editor fixed the handler for the Rotate shortcut key ('R') --- FlatCAMApp.py | 55 ++++++++++++------- FlatCAMEditor.py | 97 +++++++++++++++++++++++++-------- FlatCAMGUI.py | 49 ++++++++++++----- README.md | 3 + flatcamTools/ToolCalculators.py | 66 +++++++++++----------- 5 files changed, 181 insertions(+), 89 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index deab459c..4f6ffe52 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -257,6 +257,8 @@ class App(QtCore.QObject): # Create multiprocessing pool self.pool = Pool() + # variable to store mouse coordinates + self.mouse = [0, 0] #################### ## Initialize GUI ## @@ -1059,7 +1061,7 @@ class App(QtCore.QObject): self.ui.menufilesavedefaults.triggered.connect(self.on_file_savedefaults) self.ui.menufile_exit.triggered.connect(self.on_app_exit) - self.ui.menueditnew.triggered.connect(lambda: self.new_object('geometry', 'new_g', lambda x, y: None)) + self.ui.menueditnew.triggered.connect(self.new_geometry_object) self.ui.menueditnewexc.triggered.connect(self.new_excellon_object) self.ui.menueditedit.triggered.connect(self.object2editor) self.ui.menueditok.triggered.connect(self.editor2object) @@ -1140,7 +1142,7 @@ class App(QtCore.QObject): self.ui.zoom_in_btn.triggered.connect(lambda: self.plotcanvas.zoom(1 / 1.5)) self.ui.zoom_out_btn.triggered.connect(lambda: self.plotcanvas.zoom(1.5)) - self.ui.newgeo_btn.triggered.connect(lambda: self.new_object('geometry', 'new_g', lambda x, y: None)) + self.ui.newgeo_btn.triggered.connect(self.new_geometry_object) self.ui.newexc_btn.triggered.connect(self.new_excellon_object) self.ui.editgeo_btn.triggered.connect(self.object2editor) self.ui.update_obj_btn.triggered.connect(self.editor2object) @@ -1150,7 +1152,7 @@ class App(QtCore.QObject): # Context Menu self.ui.popmenu_disable.triggered.connect(lambda: self.disable_plots(self.collection.get_selected())) - self.ui.popmenu_new_geo.triggered.connect(lambda: self.new_object('geometry', 'new_g', lambda x, y: None)) + self.ui.popmenu_new_geo.triggered.connect(self.new_geometry_object) self.ui.popmenu_new_exc.triggered.connect(self.new_excellon_object) self.ui.popmenu_new_prj.triggered.connect(self.on_file_new) @@ -1665,7 +1667,7 @@ class App(QtCore.QObject): edited_obj.plot() self.ui.plot_tab_area.setTabText(0, "Plot Area") self.ui.plot_tab_area.protectTab(0) - self.inform.emit("[success] %s is updated, returning to App..." % obj_type) + self.inform.emit("[selected] %s is updated, returning to App..." % obj_type) # reset the Object UI to original settings # edited_obj.set_ui(edited_obj.ui_type()) @@ -2271,6 +2273,8 @@ class App(QtCore.QObject): obj.options['ymax'] = ymax except: log.warning("The object has no bounds properties.") + # don't plot objects with no bounds, there is nothing to plot + self.plot = False pass FlatCAMApp.App.log.debug("Moving new object back to main thread.") @@ -2284,7 +2288,15 @@ class App(QtCore.QObject): def new_excellon_object(self): self.report_usage("new_excellon_object()") - self.new_object('excellon', 'new_e', lambda x, y: None) + self.new_object('excellon', 'new_e', lambda x, y: None, plot=False) + + def new_geometry_object(self): + self.report_usage("new_geometry_object()") + + def initialize(obj, self): + obj.multitool = False + + self.new_object('geometry', 'new_g', initialize, plot=False) def on_object_created(self, obj, plot, autoselect): """ @@ -2302,8 +2314,20 @@ class App(QtCore.QObject): # after adding the object to the collection always update the list of objects that are in the collection self.all_objects_list = self.collection.get_list() - self.inform.emit("[success]Object (%s) created: %s" % (obj.kind, obj.options['name'])) - self.new_object_available.emit(obj) + + if obj.kind == 'gerber': + self.inform.emit('[selected]%s created/selected: %s' % + (obj.kind.capitalize(), 'green', str(obj.options['name']))) + elif obj.kind == 'excellon': + self.inform.emit('[selected]%s created/selected: %s' % + (obj.kind.capitalize(), 'brown', str(obj.options['name']))) + elif obj.kind == 'cncjob': + self.inform.emit('[selected]%s created/selected: %s' % + (obj.kind.capitalize(), 'blue', str(obj.options['name']))) + 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']) @@ -2327,7 +2351,7 @@ class App(QtCore.QObject): # Send to worker # self.worker.add_task(worker_task, [self]) - if plot: + if plot is True: self.worker_task.emit({'fcn': worker_task, 'params': [obj]}) def on_object_changed(self, obj): @@ -3560,15 +3584,6 @@ class App(QtCore.QObject): # Mark end of undo block cursor.endEditBlock() - def on_new_geometry(self): - self.report_usage("on_new_geometry()") - - def initialize(obj, self): - obj.multitool = False - - self.new_object('geometry', 'new_g', initialize) - self.plot_all() - def on_delete(self): """ Delete the currently selected FlatCAMObjs. @@ -4362,11 +4377,9 @@ class App(QtCore.QObject): def on_double_click_over_plot(self, event): # make double click work only for the LMB if event.button == 1: - if not self.collection.get_selected(): - pass - else: + if self.collection.get_selected(): self.ui.notebook.setCurrentWidget(self.ui.selected_tab) - #delete the selection shape(S) as it may be in the way + # delete the selection shape(S) as it may be in the way self.delete_selection_shape() def on_mouse_move_over_plot(self, event, origin_click=None): diff --git a/FlatCAMEditor.py b/FlatCAMEditor.py index 38c333b8..3b94c51c 100644 --- a/FlatCAMEditor.py +++ b/FlatCAMEditor.py @@ -583,6 +583,8 @@ class FCCircle(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_circle' + self.start_msg = "Click on CENTER ..." self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"] @@ -620,6 +622,8 @@ class FCCircle(FCShapeTool): class FCArc(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_arc' + self.start_msg = "Click on CENTER ..." # Direction of rotation between point 1 and 2. @@ -808,6 +812,8 @@ class FCRectangle(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_rectangle' + self.start_msg = "Click on 1st corner ..." def click(self, point): @@ -846,6 +852,8 @@ class FCPolygon(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_polygon' + self.start_msg = "Click on 1st point ..." def click(self, point): @@ -891,6 +899,8 @@ class FCPath(FCPolygon): def make(self): self.geometry = DrawToolShape(LineString(self.points)) + self.name = 'fc_path' + self.draw_app.in_action = False self.complete = True self.draw_app.app.inform.emit("[success]Done. Path completed.") @@ -912,6 +922,8 @@ class FCPath(FCPolygon): class FCSelect(DrawTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_select' + self.storage = self.draw_app.storage # self.shape_buffer = self.draw_app.shape_buffer # self.selected = self.draw_app.selected @@ -989,6 +1001,7 @@ class FCSelect(DrawTool): class FCDrillSelect(DrawTool): def __init__(self, exc_editor_app): DrawTool.__init__(self, exc_editor_app) + self.name = 'fc_drill_select' self.exc_editor_app = exc_editor_app self.storage = self.exc_editor_app.storage_dict @@ -1146,6 +1159,8 @@ class FCDrillSelect(DrawTool): class FCMove(FCShapeTool): def __init__(self, draw_app): FCShapeTool.__init__(self, draw_app) + self.name = 'fc_move' + # self.shape_buffer = self.draw_app.shape_buffer self.origin = None self.destination = None @@ -1211,6 +1226,9 @@ class FCMove(FCShapeTool): class FCCopy(FCMove): + def __init__(self, draw_app): + FCMove.__init__(self, draw_app) + self.name = 'fc_copy' def make(self): # Create new geometry @@ -1225,6 +1243,8 @@ class FCCopy(FCMove): class FCText(FCShapeTool): def __init__(self, draw_app): FCShapeTool.__init__(self, draw_app) + self.name = 'fc_text' + # self.shape_buffer = self.draw_app.shape_buffer self.draw_app = draw_app self.app = draw_app.app @@ -1275,6 +1295,8 @@ class FCText(FCShapeTool): class FCBuffer(FCShapeTool): def __init__(self, draw_app): FCShapeTool.__init__(self, draw_app) + self.name = 'fc_buffer' + # self.shape_buffer = self.draw_app.shape_buffer self.draw_app = draw_app self.app = draw_app.app @@ -1341,6 +1363,8 @@ class FCBuffer(FCShapeTool): class FCPaint(FCShapeTool): def __init__(self, draw_app): FCShapeTool.__init__(self, draw_app) + self.name = 'fc_paint' + # self.shape_buffer = self.draw_app.shape_buffer self.draw_app = draw_app self.app = draw_app.app @@ -1355,6 +1379,7 @@ class FCPaint(FCShapeTool): class FCRotate(FCShapeTool): def __init__(self, draw_app): FCShapeTool.__init__(self, draw_app) + self.name = 'fc_rotate' geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) @@ -1366,7 +1391,6 @@ class FCRotate(FCShapeTool): def set_origin(self, origin): self.origin = origin - def make(self): # Create new geometry # dx = self.origin[0] @@ -1382,9 +1406,9 @@ class FCRotate(FCShapeTool): #self.draw_app.select_tool("select") def on_key(self, key): - if key == 'Enter': - if self.complete == True: - self.make() + if key == 'Enter' or key == QtCore.Qt.Key_Enter: + self.make() + return "Done" def click(self, point): self.make() @@ -1408,6 +1432,7 @@ class FCDrillAdd(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_drill_add' self.selected_dia = None try: @@ -1443,11 +1468,17 @@ class FCDrillAdd(FCShapeTool): return DrawToolUtilityShape(self.util_shape(data)) def util_shape(self, point): + if point[0] is None and point[1] is None: + point_x = self.draw_app.x + point_y = self.draw_app.y + else: + point_x = point[0] + point_y = point[1] - start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1]) - stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1]) - start_vert_line = (point[0], (point[1] - (self.selected_dia / 2))) - stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2))) + start_hor_line = ((point_x - (self.selected_dia / 2)), point_y) + stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y) + start_vert_line = (point_x, (point_y - (self.selected_dia / 2))) + stop_vert_line = (point_x, (point_y + (self.selected_dia / 2))) return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)]) @@ -1473,6 +1504,7 @@ class FCDrillArray(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_drill_array' self.draw_app.array_frame.show() @@ -1558,17 +1590,16 @@ class FCDrillArray(FCShapeTool): return if self.drill_array == 'Linear': - # if self.origin is None: - # self.origin = (0, 0) - # - # dx = data[0] - self.origin[0] - # dy = data[1] - self.origin[1] - dx = data[0] - dy = data[1] + if data[0] is None and data[1] is None: + dx = self.draw_app.x + dy = self.draw_app.y + else: + dx = data[0] + dy = data[1] geo_list = [] geo = None - self.points = data + self.points = [dx, dy] for item in range(self.drill_array_size): if self.drill_axis == 'X': @@ -1592,16 +1623,30 @@ class FCDrillArray(FCShapeTool): self.last_dy = dy return DrawToolUtilityShape(geo_list) else: + if data[0] is None and data[1] is None: + cdx = self.draw_app.x + cdy = self.draw_app.y + else: + cdx = data[0] + cdy = data[1] + if len(self.pt) > 0: temp_points = [x for x in self.pt] - temp_points.append(data) + temp_points.append([cdx, cdy]) return DrawToolUtilityShape(LineString(temp_points)) def util_shape(self, point): - start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1]) - stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1]) - start_vert_line = (point[0], (point[1] - (self.selected_dia / 2))) - stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2))) + if point[0] is None and point[1] is None: + point_x = self.draw_app.x + point_y = self.draw_app.y + else: + point_x = point[0] + point_y = point[1] + + start_hor_line = ((point_x - (self.selected_dia / 2)), point_y) + stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y) + start_vert_line = (point_x, (point_y - (self.selected_dia / 2))) + stop_vert_line = (point_x, (point_y + (self.selected_dia / 2))) return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)]) @@ -1656,10 +1701,12 @@ class FCDrillArray(FCShapeTool): self.draw_app.array_frame.hide() return -class FCDrillResize(FCShapeTool): +class FCDrillResize(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_drill_resize' + self.draw_app.app.inform.emit("Click on the Drill(s) to resize ...") self.resize_dia = None self.draw_app.resize_frame.show() @@ -1761,6 +1808,8 @@ class FCDrillResize(FCShapeTool): class FCDrillMove(FCShapeTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = 'fc_drill_move' + # self.shape_buffer = self.draw_app.shape_buffer self.origin = None self.destination = None @@ -1850,6 +1899,9 @@ class FCDrillMove(FCShapeTool): class FCDrillCopy(FCDrillMove): + def __init__(self, draw_app): + FCDrillMove.__init__(self, draw_app) + self.name = 'fc_drill_copy' def make(self): # Create new geometry @@ -1977,6 +2029,7 @@ class FlatCAMGeoEditor(QtCore.QObject): self.geo_key_modifiers = None self.x = None # Current mouse cursor pos self.y = None + # Current snapped mouse pos self.snap_x = None self.snap_y = None diff --git a/FlatCAMGUI.py b/FlatCAMGUI.py index 9065d647..91690f9b 100644 --- a/FlatCAMGUI.py +++ b/FlatCAMGUI.py @@ -1738,7 +1738,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # New Geometry if key == QtCore.Qt.Key_N: - self.app.on_new_geometry() + self.app.new_geometry_object() # Set Origin if key == QtCore.Qt.Key_O: @@ -1820,15 +1820,26 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # complete automatically, like a polygon or path. if key == QtCore.Qt.Key_Enter or key == 'Enter': if isinstance(self.app.geo_editor.active_tool, FCShapeTool): - self.app.geo_editor.active_tool.click( - self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y)) - self.app.geo_editor.active_tool.make() - if self.app.geo_editor.active_tool.complete: - self.app.geo_editor.on_shape_complete() - self.app.inform.emit("[success]Done.") - # automatically make the selection tool active after completing current action - self.app.geo_editor.select_tool('select') - return + if self.app.geo_editor.active_tool.name == 'fc_rotate': + self.app.geo_editor.active_tool.make() + + if self.app.geo_editor.active_tool.complete: + self.app.geo_editor.on_shape_complete() + self.app.inform.emit("[success]Done.") + # automatically make the selection tool active after completing current action + self.app.geo_editor.select_tool('select') + return + else: + self.app.geo_editor.active_tool.click( + self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y)) + + self.app.geo_editor.active_tool.make() + + if self.app.geo_editor.active_tool.complete: + self.app.geo_editor.on_shape_complete() + self.app.inform.emit("[success]Done.") + # automatically make the selection tool active after completing current action + self.app.geo_editor.select_tool('select') # Abort the current action if key == QtCore.Qt.Key_Escape or key == 'Escape': @@ -1887,7 +1898,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_C or key == 'C': self.app.ui.geo_copy_btn.setChecked(True) self.app.geo_editor.on_tool_select('copy') - self.app.geo_editor.active_tool.set_origin(self.snap(self.x, self.y)) + self.app.geo_editor.active_tool.set_origin(self.app.geo_editor.snap( + self.app.geo_editor.x, self.app.geo_editor.y)) self.app.inform.emit("Click on target point.") # Substract Tool @@ -2004,7 +2016,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Propagate to tool response = None if self.app.geo_editor.active_tool is not None: - response = self.app.geo_editor.active_tool.on_key(event) + response = self.app.geo_editor.active_tool.on_key(key=key) if response is not None: self.app.inform.emit(response) @@ -2094,6 +2106,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.exc_editor.launched_from_shortcuts = True self.app.inform.emit("Click on target point.") self.app.ui.add_drill_array_btn.setChecked(True) + + self.app.exc_editor.x = self.app.mouse[0] + self.app.exc_editor.y = self.app.mouse[1] + self.app.exc_editor.select_tool('add_array') return @@ -2115,6 +2131,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.exc_editor.launched_from_shortcuts = True self.app.inform.emit("Click on target point.") self.app.ui.add_drill_btn.setChecked(True) + + self.app.exc_editor.x = self.app.mouse[0] + self.app.exc_editor.y = self.app.mouse[1] + self.app.exc_editor.select_tool('add') return @@ -2142,7 +2162,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.app.inform.emit("Click on target point.") self.app.ui.move_drill_btn.setChecked(True) self.app.exc_editor.on_tool_select('move') - self.app.exc_editor.active_tool.set_origin((self.snap_x, self.snap_y)) + self.app.exc_editor.active_tool.set_origin( + (self.app.exc_editor.snap_x, self.app.exc_editor.snap_y)) else: self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to move.") return @@ -2182,7 +2203,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Propagate to tool response = None if self.app.exc_editor.active_tool is not None: - response = self.app.exc_editor.active_tool.on_key(event) + response = self.app.exc_editor.active_tool.on_key(key=key) if response is not None: self.app.inform.emit(response) diff --git a/README.md b/README.md index dbc03b4f..24f67c8f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ CAD program, and create G-Code for Isolation routing. - added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory - added into Preferences the Calculator Tools - made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before) +- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor +- changed the messages from status bar on new object creation/selection +- in Geometry Editor fixed the handler for the Rotate shortcut key ('R') 8.02.2019 diff --git a/flatcamTools/ToolCalculators.py b/flatcamTools/ToolCalculators.py index fc09ebd6..e74393ed 100644 --- a/flatcamTools/ToolCalculators.py +++ b/flatcamTools/ToolCalculators.py @@ -21,6 +21,40 @@ class ToolCalculator(FlatCAMTool): title_label = QtWidgets.QLabel("%s" % self.toolName) self.layout.addWidget(title_label) + ###################### + ## Units Calculator ## + ###################### + + self.unists_spacer_label = QtWidgets.QLabel(" ") + self.layout.addWidget(self.unists_spacer_label) + + ## Title of the Units Calculator + units_label = QtWidgets.QLabel("%s" % self.unitsName) + self.layout.addWidget(units_label) + + #Grid Layout + grid_units_layout = QtWidgets.QGridLayout() + self.layout.addLayout(grid_units_layout) + + inch_label = QtWidgets.QLabel("INCH") + mm_label = QtWidgets.QLabel("MM") + grid_units_layout.addWidget(mm_label, 0, 0) + grid_units_layout.addWidget( inch_label, 0, 1) + + self.inch_entry = FCEntry() + # self.inch_entry.setFixedWidth(70) + self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.inch_entry.setToolTip("Here you enter the value to be converted from INCH to MM") + + self.mm_entry = FCEntry() + # self.mm_entry.setFixedWidth(130) + self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.mm_entry.setToolTip("Here you enter the value to be converted from MM to INCH") + + grid_units_layout.addWidget(self.mm_entry, 1, 0) + grid_units_layout.addWidget(self.inch_entry, 1, 1) + + ############################ ## V-shape Tool Calculator ## ############################ @@ -83,38 +117,6 @@ class ToolCalculator(FlatCAMTool): form_layout.addRow(self.empty_label, self.calculate_vshape_button) - ###################### - ## Units Calculator ## - ###################### - - self.unists_spacer_label = QtWidgets.QLabel(" ") - self.layout.addWidget(self.unists_spacer_label) - - ## Title of the Units Calculator - units_label = QtWidgets.QLabel("%s" % self.unitsName) - self.layout.addWidget(units_label) - - #Grid Layout - grid_units_layout = QtWidgets.QGridLayout() - self.layout.addLayout(grid_units_layout) - - inch_label = QtWidgets.QLabel("INCH") - mm_label = QtWidgets.QLabel("MM") - grid_units_layout.addWidget(mm_label, 0, 0) - grid_units_layout.addWidget( inch_label, 0, 1) - - self.inch_entry = FCEntry() - # self.inch_entry.setFixedWidth(70) - self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.inch_entry.setToolTip("Here you enter the value to be converted from INCH to MM") - - self.mm_entry = FCEntry() - # self.mm_entry.setFixedWidth(130) - self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.mm_entry.setToolTip("Here you enter the value to be converted from MM to INCH") - - grid_units_layout.addWidget(self.mm_entry, 1, 0) - grid_units_layout.addWidget(self.inch_entry, 1, 1) #################################### ## ElectroPlating Tool Calculator ##
~F3  SHOW SHORTCUT LIST
Del  Delete Obj
'`' (left to Key_1)Toogle Notebook Area (Left Side)
SPACE  En(Dis)able Obj PlotS  Shell Toggle
T Add a Tool (when in Geometry Selected Tab)
V  Zoom Fit
ALT+R Transformation Tool Transformations Tool
ALT+UR  Resize Drill(s)
T Add a new Tool
   
T Add a Tool (when in Geometry Selected Tab) Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)
V