diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 40e94743..90e110b6 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -29,7 +29,7 @@ import gc from xml.dom.minidom import parseString as parse_xml_string from multiprocessing.connection import Listener, Client -from multiprocessing import Pool +from multiprocessing import Pool, cpu_count import socket from array import array @@ -376,7 +376,7 @@ class App(QtCore.QObject): # ############################################################################# # ##################### CREATE MULTIPROCESSING POOL ########################### # ############################################################################# - self.pool = Pool() + self.pool = Pool(processes=cpu_count()) # ########################################################################## # ################## Setting the Splash Screen ############################# @@ -1227,9 +1227,14 @@ class App(QtCore.QObject): 'minoffset, multidepth, name, offset, opt_type, order, outname, overlap, ' 'passes, postamble, pp, ppname_e, ppname_g, preamble, radius, ref, rest, ' 'rows, shellvar_, scale_factor, spacing_columns, spacing_rows, spindlespeed, ' - 'toolchange_xy, use_threads, value, x, x0, x1, y, y0, y1, z_cut, z_move' -, - + 'toolchange_xy, use_threads, value, x, x0, x1, y, y0, y1, z_cut, z_move', + "script_autocompleter": True, + "script_text": "", + "script_plot": True, + "script_source_file": "", + "document_text": "", + "document_plot": True, + "document_source_file": "", }) # ############################################################ @@ -1514,9 +1519,11 @@ class App(QtCore.QObject): "tools_panelize_constrainy": 0.0, "script_text": "", - "script_plot": True, - "notes_text": "", - "notes_plot": True, + "script_plot": False, + "script_source_file": "", + "document_text": "", + "document_plot": False, + "document_source_file": "", }) @@ -3955,7 +3962,7 @@ class App(QtCore.QObject): "cncjob": FlatCAMCNCjob, "geometry": FlatCAMGeometry, "script": FlatCAMScript, - "notes": FlatCAMNotes + "document": FlatCAMDocument } App.log.debug("Calling object constructor...") @@ -4019,8 +4026,8 @@ class App(QtCore.QObject): self.log.debug("%f seconds converting units." % (t3 - t2)) # Create the bounding box for the object and then add the results to the obj.options - # But not for Scripts or for Notes - if kind != 'notes' and kind != 'script': + # But not for Scripts or for Documents + if kind != 'document' and kind != 'script': try: xmin, ymin, xmax, ymax = obj.bounds() obj.options['xmin'] = xmin @@ -4031,6 +4038,9 @@ class App(QtCore.QObject): log.warning("The object has no bounds properties. %s" % str(e)) return "fail" + # update the KeyWords list with the name of the file + self.myKeywords.append(obj.options['name']) + FlatCAMApp.App.log.debug("Moving new object back to main thread.") # Move the object to the main thread and let the app know that it is available. @@ -4089,7 +4099,7 @@ class App(QtCore.QObject): self.new_object('gerber', 'new_grb', initialize, plot=False) - def new_script_object(self, name=None): + def new_script_object(self, name=None, text=None): """ Creates a new, blank TCL Script object. :param name: a name for the new object @@ -4097,21 +4107,30 @@ class App(QtCore.QObject): """ self.report_usage("new_script_object()") - def initialize(obj, self): - obj.source_file = _( - "#\n" - "# CREATE A NEW FLATCAM TCL SCRIPT\n" - "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n" - "#\n\n" - "# FlatCAM commands list:\n" - "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, AlignDrillGrid, ClearShell, ClearCopper,\n" - "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, GeoCutout, GeoUnion, GetNames,\n" - "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, JoinGeometry, ListSys, MillDrills,\n" - "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" - "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, SetSys, Skew, SubtractPoly,\n" - "# SubtractRectangle, Version, WriteGCode\n" - "#\n\n" + if text is not None: + new_source_file = text + else: + new_source_file = _( + "#\n" + "# CREATE A NEW FLATCAM TCL SCRIPT\n" + "# TCL Tutorial here: https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n" + "#\n\n" + "# FlatCAM commands list:\n" + "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, AlignDrillGrid, ClearShell, " + "ClearCopper,\n" + "# Cncjob, Cutout, Delete, Drillcncjob, ExportGcode, ExportSVG, Exteriors, GeoCutout, GeoUnion, " + "GetNames,\n" + "# GetSys, ImportSvg, Interiors, Isolate, Follow, JoinExcellon, JoinGeometry, ListSys, MillDrills,\n" + "# MillSlots, Mirror, New, NewGeometry, Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" + "# Options, Paint, Panelize, Plot, SaveProject, SaveSys, Scale, SetActive, SetSys, Skew, " + "SubtractPoly,\n" + "# SubtractRectangle, Version, WriteGCode\n" + "#\n\n" ) + + def initialize(obj, self): + obj.source_file = deepcopy(new_source_file) + if name is None: outname = 'new_script' else: @@ -4119,19 +4138,18 @@ class App(QtCore.QObject): self.new_object('script', outname, initialize, plot=False) - - def new_notes_object(self): + def new_document_object(self): """ - Creates a new, blank Notes object. + Creates a new, blank Document object. :return: None """ - self.report_usage("new_notes_object()") + self.report_usage("new_document_object()") def initialize(obj, self): obj.source_file = "" - self.new_object('notes', 'new_notes', initialize, plot=False) + self.new_object('document', 'new_document', initialize, plot=False) def on_object_created(self, obj, plot, autoselect): """ @@ -4169,12 +4187,11 @@ class App(QtCore.QObject): elif obj.kind == 'script': self.inform.emit(_('[selected] {kind} created/selected: {name}').format( kind=obj.kind.capitalize(), color='orange', name=str(obj.options['name']))) - elif obj.kind == 'notes': + elif obj.kind == 'document': self.inform.emit(_('[selected] {kind} created/selected: {name}').format( kind=obj.kind.capitalize(), color='violet', name=str(obj.options['name']))) # update the SHELL auto-completer model with the name of the new object - self.myKeywords.append(obj.options['name']) self.shell._edit.set_model_data(self.myKeywords) if autoselect: @@ -6971,6 +6988,12 @@ class App(QtCore.QObject): obj_init.slots = deepcopy(obj.slots) obj_init.create_geometry() + def initialize_script(obj_init, app_obj): + obj_init.source_file = deepcopy(obj.source_file) + + def initialize_document(obj_init, app_obj): + obj_init.source_file = deepcopy(obj.source_file) + for obj in self.collection.get_selected(): obj_name = obj.options["name"] @@ -6981,6 +7004,10 @@ class App(QtCore.QObject): self.new_object("gerber", str(obj_name) + "_copy", initialize) elif isinstance(obj, FlatCAMGeometry): self.new_object("geometry", str(obj_name) + "_copy", initialize) + elif isinstance(obj, FlatCAMScript): + self.new_object("script", str(obj_name) + "_copy", initialize_script) + elif isinstance(obj, FlatCAMDocument): + self.new_object("document", str(obj_name) + "_copy", initialize_document) except Exception as e: return "Operation failed: %s" % str(e) @@ -8376,6 +8403,10 @@ class App(QtCore.QObject): obj.on_exportgcode_button_click() elif type(obj) == FlatCAMGerber: self.on_file_savegerber() + elif type(obj) == FlatCAMScript: + self.on_file_savescript() + elif type(obj) == FlatCAMDocument: + self.on_file_savedocument() def obj_move(self): self.report_usage("obj_move()") @@ -8694,6 +8725,94 @@ class App(QtCore.QObject): self.file_opened.emit("Gerber", filename) self.file_saved.emit("Gerber", filename) + def on_file_savescript(self): + """ + Callback for menu item in Project context menu. + + :return: None + """ + self.report_usage("on_file_savescript") + App.log.debug("on_file_savescript()") + + obj = self.collection.get_active() + if obj is None: + self.inform.emit('[WARNING_NOTCL] %s' % + _("No object selected. Please select an Script object to export.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, FlatCAMScript): + self.inform.emit('[ERROR_NOTCL] %s' % + _("Failed. Only Script objects can be saved as TCL Script files...")) + return + + name = self.collection.get_active().options["name"] + + _filter = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getSaveFileName( + caption="Save Script source file", + directory=self.get_last_save_folder() + '/' + name, + filter=_filter) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Script source file"), filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % + _("Save Script source file cancelled.")) + return + else: + self.save_source_file(name, filename) + if self.defaults["global_open_style"] is False: + self.file_opened.emit("Script", filename) + self.file_saved.emit("Script", filename) + + def on_file_savedocument(self): + """ + Callback for menu item in Project context menu. + + :return: None + """ + self.report_usage("on_file_savedocument") + App.log.debug("on_file_savedocument()") + + obj = self.collection.get_active() + if obj is None: + self.inform.emit('[WARNING_NOTCL] %s' % + _("No object selected. Please select an Document object to export.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, FlatCAMScript): + self.inform.emit('[ERROR_NOTCL] %s' % + _("Failed. Only Document objects can be saved as Document files...")) + return + + name = self.collection.get_active().options["name"] + + _filter = "FlatCAM Documents (*.FlatDoc);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getSaveFileName( + caption="Save Document source file", + directory=self.get_last_save_folder() + '/' + name, + filter=_filter) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Save Document source file"), filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % + _("Save Document source file cancelled.")) + return + else: + self.save_source_file(name, filename) + if self.defaults["global_open_style"] is False: + self.file_opened.emit("Document", filename) + self.file_saved.emit("Document", filename) + def on_file_saveexcellon(self): """ Callback for menu item in project context menu. @@ -9090,65 +9209,22 @@ class App(QtCore.QObject): self.inform.emit('[success] %s' % _("New TCL script file created in Code Editor.")) - flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" - - self.proc_container.view.set_busy(_("Loading...")) - - self.script_editor_tab = TextEditor(app=self) - - # add the tab if it was closed - self.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor")) - self.script_editor_tab.setObjectName('script_editor_tab') - # delete the absolute and relative position and messages in the infobar self.ui.position_label.setText("") self.ui.rel_position_label.setText("") - # first clear previous text in text editor (if any) - self.script_editor_tab.code_editor.clear() - self.script_editor_tab.code_editor.setReadOnly(False) - - self.script_editor_tab.code_editor.completer_enable = True - self.script_editor_tab.buttonRun.show() - - # Switch plot_area to CNCJob tab - self.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab) - - self.script_editor_tab.buttonOpen.clicked.disconnect() - self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt)) - self.script_editor_tab.buttonSave.clicked.disconnect() - self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt)) - - try: - self.script_editor_tab.buttonRun.clicked.disconnect() - except TypeError: - pass - self.script_editor_tab.buttonRun.clicked.connect(self.script_editor_tab.handleRunCode) - - self.script_editor_tab.handleTextChanged() - if name is not None: - self.new_script_object(name=name) - script_obj = self.collection.get_by_name(name) + self.new_script_object(name=name, text=text) else: - self.new_script_object() - script_obj = self.collection.get_by_name('new_script') + self.new_script_object(text=text) - script_text = script_obj.source_file - - self.script_editor_tab.t_frame.hide() - if text is not None: - try: - for line in text: - self.script_editor_tab.code_editor.append(line) - except TypeError: - self.script_editor_tab.code_editor.append(text) - - else: - self.script_editor_tab.code_editor.append(script_text) - self.script_editor_tab.t_frame.show() - - self.proc_container.view.set_idle() + # script_text = script_obj.source_file + # + # self.proc_container.view.set_busy(_("Loading...")) + # script_obj.script_editor_tab.t_frame.hide() + # + # script_obj.script_editor_tab.t_frame.show() + # self.proc_container.view.set_idle() def on_fileopenscript(self, name=None, silent=False): """ @@ -9158,53 +9234,29 @@ class App(QtCore.QObject): :param name: name of a Tcl script file to open :return: """ - script_content = [] + + self.report_usage("on_fileopenscript") + App.log.debug("on_fileopenscript()") + + _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)" if name: - filename = name + filenames = [name] else: - _filter_ = "TCL script (*.FlatScript);;TCL script (*.TCL);;TCL script (*.TXT);;All Files (*.*)" try: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open TCL script"), + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), directory=self.get_last_folder(), filter=_filter_) except TypeError: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open TCL script"), filter=_filter_) + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_) - # The Qt methods above will return a QString which can cause problems later. - # So far json.dump() will fail to serialize it. - # TODO: Improve the serialization methods and remove this fix. - filename = str(filename) - if filename == "": + if len(filenames) == 0: if silent is False: self.inform.emit('[WARNING_NOTCL] %s' % _("Open TCL script cancelled.")) else: - self.proc_container.view.set_busy(_("Loading...")) - - try: - with open(filename, "r") as opened_script: - try: - for line in opened_script: - QtWidgets.QApplication.processEvents() - proc_line = str(line).strip('\n') - script_content.append(proc_line) - except Exception as e: - log.debug('App.on_fileopenscript() -->%s' % str(e)) - if silent is False: - self.inform.emit('[ERROR] %s %s' % - ('App.on_fileopenscript() -->', str(e))) - return - - if silent is False: - self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor.")) - - except Exception as e: - log.debug("App.on_fileopenscript() -> %s" % str(e)) - - self.proc_container.view.set_idle() - - script_name = filename.split('/')[-1].split('\\')[-1] - self.on_filenewscript(name=script_name, text=script_content) + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_script, 'params': [filename]}) def on_filerunscript(self, name=None, silent=False): """ @@ -10353,8 +10405,6 @@ class App(QtCore.QObject): assert isinstance(app_obj_, App), \ "Initializer expected App, got %s" % type(app_obj_) - self.progress.emit(10) - try: f = open(filename) gcode = f.read() @@ -10362,20 +10412,16 @@ class App(QtCore.QObject): except IOError: app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open"), filename)) - self.progress.emit(0) return "fail" job_obj.gcode = gcode - self.progress.emit(20) - ret = job_obj.gcode_parse() if ret == "fail": self.inform.emit('[ERROR_NOTCL] %s' % _("This is not GCODE")) return "fail" - self.progress.emit(60) job_obj.create_geometry() with self.proc_container.new(_("Opening G-Code.")): @@ -10398,7 +10444,44 @@ class App(QtCore.QObject): # GUI feedback self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - self.progress.emit(100) + + def open_script(self, filename, outname=None, silent=False): + """ + Opens a Script file, parses it and creates a new object for + it in the program. Thread-safe. + + :param outname: Name of the resulting object. None causes the name to be that of the file. + :param filename: Script file filename + :type filename: str + :return: None + """ + App.log.debug("open_script()") + + with self.proc_container.new(_("Opening TCL Script...")): + + try: + with open(filename, "r") as opened_script: + script_content = opened_script.readlines() + script_content = ''.join(script_content) + + if silent is False: + self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor.")) + except Exception as e: + log.debug("App.open_script() -> %s" % str(e)) + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to open TCL Script.")) + return + + # Object name + script_name = outname or filename.split('/')[-1].split('\\')[-1] + + # New object creation and file processing + self.on_filenewscript(name=script_name, text=script_content) + + # Register recent file + self.file_opened.emit("script", filename) + + # GUI feedback + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) def open_config_file(self, filename, run_from_arg=None): """ @@ -10816,6 +10899,8 @@ class App(QtCore.QObject): "excellon": "share/drill16.png", 'geometry': "share/geometry16.png", "cncjob": "share/cnc16.png", + "script": "share/script_new24.png", + "document": "share/notes16_1.png", "project": "share/project16.png", "svg": "share/geometry16.png", "dxf": "share/dxf16.png", @@ -10829,6 +10914,8 @@ class App(QtCore.QObject): 'excellon': lambda fname: self.worker_task.emit({'fcn': self.open_excellon, 'params': [fname]}), 'geometry': lambda fname: self.worker_task.emit({'fcn': self.import_dxf, 'params': [fname]}), 'cncjob': lambda fname: self.worker_task.emit({'fcn': self.open_gcode, 'params': [fname]}), + "script": lambda fname: self.worker_task.emit({'fcn': self.open_script, 'params': [fname]}), + "document": None, 'project': self.open_project, 'svg': self.import_svg, 'dxf': self.import_dxf, diff --git a/FlatCAMObj.py b/FlatCAMObj.py index b54d019b..e51ca928 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -16,7 +16,10 @@ from flatcamGUI.ObjectUI import * from FlatCAMCommon import LoudDict from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy from camlib import * + import itertools +import tkinter as tk +import sys import gettext import FlatCAMTranslation as fcTranslate @@ -164,11 +167,23 @@ class FlatCAMObj(QtCore.QObject): assert isinstance(self.ui, ObjectUI) self.ui.name_entry.returnPressed.connect(self.on_name_activate) - self.ui.offset_button.clicked.connect(self.on_offset_button_click) - self.ui.scale_button.clicked.connect(self.on_scale_button_click) - - self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click) - self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) + try: + # it will raise an exception for those FlatCAM objects that do not build UI with the common elements + self.ui.offset_button.clicked.connect(self.on_offset_button_click) + except (TypeError, AttributeError): + pass + try: + self.ui.scale_button.clicked.connect(self.on_scale_button_click) + except (TypeError, AttributeError): + pass + try: + self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click) + except (TypeError, AttributeError): + pass + try: + self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) + except (TypeError, AttributeError): + pass # self.ui.skew_button.clicked.connect(self.on_skew_button_click) def build_ui(self): @@ -6481,6 +6496,9 @@ class FlatCAMScript(FlatCAMObj): FlatCAMObj.set_ui(self, ui) FlatCAMApp.App.log.debug("FlatCAMScript.set_ui()") + assert isinstance(self.ui, ScriptObjectUI), \ + "Expected a ScriptObjectUI, got %s" % type(self.ui) + self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() if self.units == "IN": @@ -6488,26 +6506,137 @@ class FlatCAMScript(FlatCAMObj): else: self.decimals = 2 + # Fill form fields only on object create + self.to_form() + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText(_( + 'Basic' + )) + else: + self.ui.level.setText(_( + 'Advanced' + )) + + self.script_editor_tab = TextEditor(app=self.app) + + # first clear previous text in text editor (if any) + self.script_editor_tab.code_editor.clear() + self.script_editor_tab.code_editor.setReadOnly(False) + + self.script_editor_tab.buttonRun.show() + + self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter']) + self.on_autocomplete_changed(state= self.app.defaults['script_autocompleter']) + + flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" + self.script_editor_tab.buttonOpen.clicked.disconnect() + self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt)) + self.script_editor_tab.buttonSave.clicked.disconnect() + self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt)) + + self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code) + + self.script_editor_tab.handleTextChanged() + + self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed) + + # add the source file to the Code Editor + for line in self.source_file.splitlines(): + self.script_editor_tab.code_editor.append(line) + + self.build_ui() + def build_ui(self): - pass + FlatCAMObj.build_ui(self) + tab_here = False + + # try to not add too many times a tab that it is already installed + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']: + tab_here = True + break + + # add the tab if it is not already added + if tab_here is False: + self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor")) + self.script_editor_tab.setObjectName(self.options['name']) + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab) + + def handle_run_code(self): + # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell + # tries to print on a hidden widget, therefore show the dock if hidden + if self.app.ui.shell_dock.isHidden(): + self.app.ui.shell_dock.show() + + self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText()) + + old_line = '' + for tcl_command_line in self.script_code.splitlines(): + # do not process lines starting with '#' = comment and empty lines + if not tcl_command_line.startswith('#') and tcl_command_line != '': + # id FlatCAM is run in Windows then replace all the slashes with + # the UNIX style slash that TCL understands + if sys.platform == 'win32': + if "open" in tcl_command_line: + tcl_command_line = tcl_command_line.replace('\\', '/') + + if old_line != '': + new_command = old_line + tcl_command_line + '\n' + else: + new_command = tcl_command_line + + # execute the actual Tcl command + try: + self.app.shell.open_proccessing() # Disables input box. + + result = self.app.tcl.eval(str(new_command)) + if result != 'None': + self.app.shell.append_output(result + '\n') + + old_line = '' + except tk.TclError: + old_line = old_line + tcl_command_line + '\n' + except Exception as e: + log.debug("App.handleRunCode() --> %s" % str(e)) + + if old_line != '': + # it means that the script finished with an error + result = self.app.tcl.eval("set errorInfo") + log.error("Exec command Exception: %s" % (result + '\n')) + self.app.shell.append_error('ERROR: ' + result + '\n') + + self.app.shell.close_proccessing() + + def on_autocomplete_changed(self, state): + if state: + self.script_editor_tab.code_editor.completer_enable = True + else: + self.script_editor_tab.code_editor.completer_enable = False -class FlatCAMNotes(FlatCAMObj): +class FlatCAMDocument(FlatCAMObj): """ - Represents a Notes object. + Represents a Document object. """ optionChanged = QtCore.pyqtSignal(str) - ui_type = NotesObjectUI + ui_type = DocumentObjectUI def __init__(self, name): - FlatCAMApp.App.log.debug("Creating a Notes object...") + FlatCAMApp.App.log.debug("Creating a Document object...") FlatCAMObj.__init__(self, name) - self.kind = "notes" + self.kind = "document" def set_ui(self, ui): FlatCAMObj.set_ui(self, ui) - FlatCAMApp.App.log.debug("FlatCAMNotes.set_ui()") + FlatCAMApp.App.log.debug("FlatCAMDocument.set_ui()") + + assert isinstance(self.ui, DocumentObjectUI), \ + "Expected a DocumentObjectUI, got %s" % type(self.ui) self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() @@ -6516,7 +6645,22 @@ class FlatCAMNotes(FlatCAMObj): else: self.decimals = 2 + # Fill form fields only on object create + self.to_form() + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText(_( + 'Basic' + )) + else: + self.ui.level.setText(_( + 'Advanced' + )) + + self.build_ui() + def build_ui(self): - pass + FlatCAMObj.build_ui(self) # end of file diff --git a/ObjectCollection.py b/ObjectCollection.py index 7d5e82e5..33eccd38 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -188,7 +188,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): ("geometry", "Geometry"), ("cncjob", "CNC Job"), ("script", "Scripts"), - ("notes", "Notes"), + ("document", "Document"), ] classdict = { @@ -197,7 +197,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): "cncjob": FlatCAMCNCjob, "geometry": FlatCAMGeometry, "script": FlatCAMScript, - "notes": FlatCAMNotes + "document": FlatCAMDocument } icon_files = { @@ -206,7 +206,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): "cncjob": "share/cnc16.png", "geometry": "share/geometry16.png", "script": "share/script_new16.png", - "notes": "share/notes16_1.png" + "document": "share/notes16_1.png" } root_item = None @@ -328,6 +328,14 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.app.ui.menuprojectedit.setVisible(False) if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob: self.app.ui.menuprojectviewsource.setVisible(False) + if type(obj) != FlatCAMGerber and type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and \ + type(obj) != FlatCAMCNCjob: + # meaning for Scripts and for Document type of FlatCAM object + self.app.ui.menuprojectenable.setVisible(False) + self.app.ui.menuprojectdisable.setVisible(False) + self.app.ui.menuprojectedit.setVisible(False) + self.app.ui.menuprojectproperties.setVisible(False) + self.app.ui.menuprojectgeneratecnc.setVisible(False) else: self.app.ui.menuprojectgeneratecnc.setVisible(False) @@ -576,12 +584,19 @@ class ObjectCollection(QtCore.QAbstractItemModel): # send signal with the object that is deleted # self.app.object_status_changed.emit(active.obj, 'delete') + # some objects add a Tab on creation, close it here + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.widget(idx).objectName() == active.obj.options['name']: + self.app.ui.plot_tab_area.removeTab(idx) + break + # update the SHELL auto-completer model data name = active.obj.options['name'] try: self.app.myKeywords.remove(name) self.app.shell._edit.set_model_data(self.app.myKeywords) - self.app.ui.code_editor.set_model_data(self.app.myKeywords) + # this is not needed any more because now the code editor is created on demand + # self.app.ui.code_editor.set_model_data(self.app.myKeywords) except Exception as e: log.debug( "delete_active() --> Could not remove the old object name from auto-completer model list. %s" % str(e)) @@ -742,7 +757,12 @@ class ObjectCollection(QtCore.QAbstractItemModel): elif obj.kind == 'geometry': self.app.inform.emit(_('[selected]{name} selected').format( color='red', name=str(obj.options['name']))) - + elif obj.kind == 'script': + self.app.inform.emit(_('[selected]{name} selected').format( + color='orange', name=str(obj.options['name']))) + elif obj.kind == 'document': + self.app.inform.emit(_('[selected]{name} selected').format( + color='violet', name=str(obj.options['name']))) except IndexError: # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)") self.app.inform.emit('') diff --git a/README.md b/README.md index 56bf1810..bfc79fcb 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ CAD program, and create G-Code for Isolation routing. - fixed bug in Geometry Editor that did not allow the copy of geometric elements - created a new class that holds all the Code Editor functionality and integrated as a Editor in FlatCAM, the location is in flatcamEditors folder - remade all the functions for view_source, scripts and view_code to use the new TextEditor class; now all the Code Editor tabs are being kept alive, before only one could be in an open state +- changed the name of the new object FlatCAMNotes to a more general one FlatCAMDocument +- changed the way a new FlatCAMScript object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.FlatCAMScript() class +- reused the Multiprocessing Pool declared in the App for the ToolRulesCheck() class +- adapted the Project context menu for the new types of FLatCAM objects +- modified the setup_recent_files to accommodate the new FlatCAM objects +- made sure that when an FlatCAM script object is deleted, it's associated Tab is closed 1.10.2019 diff --git a/flatcamEditors/FlatCAMTextEditor.py b/flatcamEditors/FlatCAMTextEditor.py index 0829c8fa..61e41ad0 100644 --- a/flatcamEditors/FlatCAMTextEditor.py +++ b/flatcamEditors/FlatCAMTextEditor.py @@ -1,11 +1,6 @@ from flatcamGUI.GUIElements import * from PyQt5 import QtPrintSupport -import tkinter as tk -from copy import deepcopy - -import sys - import gettext import FlatCAMTranslation as fcTranslate import builtins @@ -39,10 +34,10 @@ class TextEditor(QtWidgets.QWidget): self.code_editor = FCTextAreaExtended() stylesheet = """ - QTextEdit { selection-background-color:yellow; - selection-color:black; - } - """ + QTextEdit { selection-background-color:yellow; + selection-color:black; + } + """ self.code_editor.setStyleSheet(stylesheet) @@ -129,7 +124,6 @@ class TextEditor(QtWidgets.QWidget): self.code_editor.set_model_data(self.app.myKeywords) self.gcode_edited = '' - self.script_code = '' def handlePrint(self): self.app.report_usage("handlePrint()") @@ -269,79 +263,38 @@ class TextEditor(QtWidgets.QWidget): self.app.clipboard.setText(text) self.app.inform.emit(_("Code Editor content copied to clipboard ...")) - def handleRunCode(self): - # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell - # tries to print on a hidden widget, therefore show the dock if hidden - if self.app.ui.shell_dock.isHidden(): - self.app.ui.shell_dock.show() - - self.script_code = deepcopy(self.code_editor.toPlainText()) - - old_line = '' - for tcl_command_line in self.app.script_code.splitlines(): - # do not process lines starting with '#' = comment and empty lines - if not tcl_command_line.startswith('#') and tcl_command_line != '': - # id FlatCAM is run in Windows then replace all the slashes with - # the UNIX style slash that TCL understands - if sys.platform == 'win32': - if "open" in tcl_command_line: - tcl_command_line = tcl_command_line.replace('\\', '/') - - if old_line != '': - new_command = old_line + tcl_command_line + '\n' - else: - new_command = tcl_command_line - - # execute the actual Tcl command - try: - self.app.shell.open_proccessing() # Disables input box. - - result = self.app.tcl.eval(str(new_command)) - if result != 'None': - self.app.shell.append_output(result + '\n') - - old_line = '' - except tk.TclError: - old_line = old_line + tcl_command_line + '\n' - except Exception as e: - log.debug("App.handleRunCode() --> %s" % str(e)) - - if old_line != '': - # it means that the script finished with an error - result = self.app.tcl.eval("set errorInfo") - log.error("Exec command Exception: %s" % (result + '\n')) - self.app.shell.append_error('ERROR: ' + result + '\n') - - self.app.shell.close_proccessing() - - def closeEvent(self, QCloseEvent): - try: - self.code_editor.textChanged.disconnect() - except TypeError: - pass - try: - self.buttonOpen.clicked.disconnect() - except TypeError: - pass - try: - self.buttonPrint.clicked.disconnect() - except TypeError: - pass - try: - self.buttonPreview.clicked.disconnect() - except TypeError: - pass - try: - self.buttonFind.clicked.disconnect() - except TypeError: - pass - try: - self.buttonReplace.clicked.disconnect() - except TypeError: - pass - try: - self.button_copy_all.clicked.disconnect() - except TypeError: - pass - - super().closeEvent(QCloseEvent) + # def closeEvent(self, QCloseEvent): + # try: + # self.code_editor.textChanged.disconnect() + # except TypeError: + # pass + # try: + # self.buttonOpen.clicked.disconnect() + # except TypeError: + # pass + # try: + # self.buttonPrint.clicked.disconnect() + # except TypeError: + # pass + # try: + # self.buttonPreview.clicked.disconnect() + # except TypeError: + # pass + # try: + # self.buttonFind.clicked.disconnect() + # except TypeError: + # pass + # try: + # self.buttonReplace.clicked.disconnect() + # except TypeError: + # pass + # try: + # self.button_copy_all.clicked.disconnect() + # except TypeError: + # pass + # try: + # self.buttonRun.clicked.disconnect() + # except TypeError: + # pass + # + # super().closeEvent(QCloseEvent) diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py index 1a4d8605..5448b42f 100644 --- a/flatcamGUI/ObjectUI.py +++ b/flatcamGUI/ObjectUI.py @@ -30,7 +30,7 @@ class ObjectUI(QtWidgets.QWidget): put UI elements in ObjectUI.custom_box (QtWidgets.QLayout). """ - def __init__(self, icon_file='share/flatcam_icon32.png', title=_('FlatCAM Object'), parent=None): + def __init__(self, icon_file='share/flatcam_icon32.png', title=_('FlatCAM Object'), parent=None, common=True): QtWidgets.QWidget.__init__(self, parent=parent) layout = QtWidgets.QVBoxLayout() @@ -74,62 +74,62 @@ class ObjectUI(QtWidgets.QWidget): # ########################### # ## Common to all objects ## # ########################### + if common is True: + # ### Scale #### + self.scale_label = QtWidgets.QLabel('%s:' % _('Scale')) + self.scale_label.setToolTip( + _("Change the size of the object.") + ) + layout.addWidget(self.scale_label) - # ### Scale #### - self.scale_label = QtWidgets.QLabel('%s:' % _('Scale')) - self.scale_label.setToolTip( - _("Change the size of the object.") - ) - layout.addWidget(self.scale_label) + self.scale_grid = QtWidgets.QGridLayout() + layout.addLayout(self.scale_grid) - self.scale_grid = QtWidgets.QGridLayout() - layout.addLayout(self.scale_grid) + # Factor + faclabel = QtWidgets.QLabel('%s:' % _('Factor')) + faclabel.setToolTip( + _("Factor by which to multiply\n" + "geometric features of this object.") + ) + self.scale_grid.addWidget(faclabel, 0, 0) + self.scale_entry = FloatEntry2() + self.scale_entry.set_value(1.0) + self.scale_grid.addWidget(self.scale_entry, 0, 1) - # Factor - faclabel = QtWidgets.QLabel('%s:' % _('Factor')) - faclabel.setToolTip( - _("Factor by which to multiply\n" - "geometric features of this object.") - ) - self.scale_grid.addWidget(faclabel, 0, 0) - self.scale_entry = FloatEntry2() - self.scale_entry.set_value(1.0) - self.scale_grid.addWidget(self.scale_entry, 0, 1) + # GO Button + self.scale_button = QtWidgets.QPushButton(_('Scale')) + self.scale_button.setToolTip( + _("Perform scaling operation.") + ) + self.scale_button.setMinimumWidth(70) + self.scale_grid.addWidget(self.scale_button, 0, 2) - # GO Button - self.scale_button = QtWidgets.QPushButton(_('Scale')) - self.scale_button.setToolTip( - _("Perform scaling operation.") - ) - self.scale_button.setMinimumWidth(70) - self.scale_grid.addWidget(self.scale_button, 0, 2) + # ### Offset #### + self.offset_label = QtWidgets.QLabel('%s:' % _('Offset')) + self.offset_label.setToolTip( + _("Change the position of this object.") + ) + layout.addWidget(self.offset_label) - # ### Offset #### - self.offset_label = QtWidgets.QLabel('%s:' % _('Offset')) - self.offset_label.setToolTip( - _("Change the position of this object.") - ) - layout.addWidget(self.offset_label) + self.offset_grid = QtWidgets.QGridLayout() + layout.addLayout(self.offset_grid) - self.offset_grid = QtWidgets.QGridLayout() - layout.addLayout(self.offset_grid) + self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector')) + self.offset_vectorlabel.setToolTip( + _("Amount by which to move the object\n" + "in the x and y axes in (x, y) format.") + ) + self.offset_grid.addWidget(self.offset_vectorlabel, 0, 0) + self.offsetvector_entry = EvalEntry2() + self.offsetvector_entry.setText("(0.0, 0.0)") + self.offset_grid.addWidget(self.offsetvector_entry, 0, 1) - self.offset_vectorlabel = QtWidgets.QLabel('%s:' % _('Vector')) - self.offset_vectorlabel.setToolTip( - _("Amount by which to move the object\n" - "in the x and y axes in (x, y) format.") - ) - self.offset_grid.addWidget(self.offset_vectorlabel, 0, 0) - self.offsetvector_entry = EvalEntry2() - self.offsetvector_entry.setText("(0.0, 0.0)") - self.offset_grid.addWidget(self.offsetvector_entry, 0, 1) - - self.offset_button = QtWidgets.QPushButton(_('Offset')) - self.offset_button.setToolTip( - _("Perform the offset operation.") - ) - self.offset_button.setMinimumWidth(70) - self.offset_grid.addWidget(self.offset_button, 0, 2) + self.offset_button = QtWidgets.QPushButton(_('Offset')) + self.offset_button.setToolTip( + _("Perform the offset operation.") + ) + self.offset_button.setMinimumWidth(70) + self.offset_grid.addWidget(self.offset_button, 0, 2) layout.addStretch() @@ -1726,286 +1726,48 @@ class ScriptObjectUI(ObjectUI): be placed in ``self.custom_box`` to preserve the layout. """ - ObjectUI.__init__(self, title=_('Script Object'), icon_file='share/cnc32.png', parent=parent) - - # Scale and offset ans skew are not available for CNCJob objects. - # Hiding from the GUI. - for i in range(0, self.scale_grid.count()): - self.scale_grid.itemAt(i).widget().hide() - self.scale_label.hide() - self.scale_button.hide() - - for i in range(0, self.offset_grid.count()): - self.offset_grid.itemAt(i).widget().hide() - self.offset_label.hide() - self.offset_button.hide() - - # ## Plot options - self.plot_options_label = QtWidgets.QLabel("%s:" % _("Plot Options")) - self.custom_box.addWidget(self.plot_options_label) - - self.cncplot_method_label = QtWidgets.QLabel("%s:" % _("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_combo = RadioSet([ - {"label": _("All"), "value": "all"}, - {"label": _("Travel"), "value": "travel"}, - {"label": _("Cut"), "value": "cut"} - ], stretch=False) - - self.annotation_label = QtWidgets.QLabel("%s:" % _("Display Annotation")) - self.annotation_label.setToolTip( - _("This selects if to display text annotation on the plot.\n" - "When checked it will display numbers in order for each end\n" - "of a travel line.") - ) - self.annotation_cb = FCCheckBox() + ObjectUI.__init__(self, title=_('Script Object'), + icon_file='share/script_new24.png', + parent=parent, + common=False) # ## Object name self.name_hlay = QtWidgets.QHBoxLayout() self.custom_box.addLayout(self.name_hlay) + name_label = QtWidgets.QLabel("%s:" % _("Name")) self.name_entry = FCEntry() self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus) self.name_hlay.addWidget(name_label) self.name_hlay.addWidget(self.name_entry) - self.t_distance_label = QtWidgets.QLabel("%s:" % _("Travelled dist.")) - self.t_distance_label.setToolTip( - _("This is the total travelled distance on X-Y plane.\n" - "In current units.") - ) - self.t_distance_entry = FCEntry() - self.t_distance_entry.setToolTip( - _("This is the total travelled distance on X-Y plane.\n" - "In current units.") - ) - self.units_label = QtWidgets.QLabel() - - self.t_time_label = QtWidgets.QLabel("%s:" % _("Estimated time")) - self.t_time_label.setToolTip( - _("This is the estimated time to do the routing/drilling,\n" - "without the time spent in ToolChange events.") - ) - self.t_time_entry = FCEntry() - self.t_time_entry.setToolTip( - _("This is the estimated time to do the routing/drilling,\n" - "without the time spent in ToolChange events.") - ) - self.units_time_label = QtWidgets.QLabel() - - f_lay = QtWidgets.QGridLayout() - f_lay.setColumnStretch(1, 1) - f_lay.setColumnStretch(2, 1) - - self.custom_box.addLayout(f_lay) - 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) - f_lay.addWidget(self.annotation_label, 1, 0) - f_lay.addWidget(self.annotation_cb, 1, 1) - f_lay.addWidget(QtWidgets.QLabel(''), 1, 2) - f_lay.addWidget(self.t_distance_label, 2, 0) - f_lay.addWidget(self.t_distance_entry, 2, 1) - f_lay.addWidget(self.units_label, 2, 2) - f_lay.addWidget(self.t_time_label, 3, 0) - f_lay.addWidget(self.t_time_entry, 3, 1) - f_lay.addWidget(self.units_time_label, 3, 2) - - self.t_distance_label.hide() - self.t_distance_entry.setVisible(False) - self.t_time_label.hide() - self.t_time_entry.setVisible(False) - - e1_lbl = QtWidgets.QLabel('') - self.custom_box.addWidget(e1_lbl) - - hlay = QtWidgets.QHBoxLayout() - self.custom_box.addLayout(hlay) - - # CNC Tools Table for plot - self.cnc_tools_table_label = QtWidgets.QLabel('%s' % _('CNC Tools Table')) - self.cnc_tools_table_label.setToolTip( - _( - "Tools in this CNCJob object used for cutting.\n" - "The tool diameter is used for plotting on canvas.\n" - "The 'Offset' entry will set an offset for the cut.\n" - "'Offset' can be inside, outside, on path (none) and custom.\n" - "'Type' entry is only informative and it allow to know the \n" - "intent of using the current tool. \n" - "It can be Rough(ing), Finish(ing) or Iso(lation).\n" - "The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n" - "ball(B), or V-Shaped(V)." - ) - ) - hlay.addWidget(self.cnc_tools_table_label) - - # Plot CB - # self.plot_cb = QtWidgets.QCheckBox('Plot') - self.plot_cb = FCCheckBox(_('Plot Object')) - self.plot_cb.setToolTip( - _("Plot (show) this object.") - ) - self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft) - hlay.addStretch() - hlay.addWidget(self.plot_cb) - - self.cnc_tools_table = FCTable() - self.custom_box.addWidget(self.cnc_tools_table) - - # self.cnc_tools_table.setColumnCount(4) - # self.cnc_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Plot', '']) - # self.cnc_tools_table.setColumnHidden(3, True) - self.cnc_tools_table.setColumnCount(7) - self.cnc_tools_table.setColumnWidth(0, 20) - self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '', - _('P')]) - self.cnc_tools_table.setColumnHidden(5, True) - # stylesheet = "::section{Background-color:rgb(239,239,245)}" - # self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet) - - # Update plot button - self.updateplot_button = QtWidgets.QPushButton(_('Update Plot')) - self.updateplot_button.setToolTip( - _("Update the plot.") - ) - self.custom_box.addWidget(self.updateplot_button) - - # #################### - # ## Export G-Code ## - # #################### - self.export_gcode_label = QtWidgets.QLabel("%s:" % _("Export CNC Code")) - self.export_gcode_label.setToolTip( - _("Export and save G-Code to\n" - "make this object to a file.") - ) - self.custom_box.addWidget(self.export_gcode_label) - - # Prepend text to GCode - prependlabel = QtWidgets.QLabel('%s:' % _('Prepend to CNC Code')) - prependlabel.setToolTip( - _("Type here any G-Code commands you would\n" - "like to add at the beginning of the G-Code file.") - ) - self.custom_box.addWidget(prependlabel) - - self.prepend_text = FCTextArea() - self.custom_box.addWidget(self.prepend_text) - - # Append text to GCode - appendlabel = QtWidgets.QLabel('%s:' % _('Append to CNC Code')) - appendlabel.setToolTip( - _("Type here any G-Code commands you would\n" - "like to append to the generated file.\n" - "I.e.: M2 (End of program)") - ) - self.custom_box.addWidget(appendlabel) - - self.append_text = FCTextArea() - self.custom_box.addWidget(self.append_text) - - self.cnc_frame = QtWidgets.QFrame() - self.cnc_frame.setContentsMargins(0, 0, 0, 0) - self.custom_box.addWidget(self.cnc_frame) - self.cnc_box = QtWidgets.QVBoxLayout() - self.cnc_box.setContentsMargins(0, 0, 0, 0) - self.cnc_frame.setLayout(self.cnc_box) - - # Toolchange Custom G-Code - self.toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code')) - self.toolchangelabel.setToolTip( - _( - "Type here any G-Code commands you would\n" - "like to be executed when Toolchange event is encountered.\n" - "This will constitute a Custom Toolchange GCode,\n" - "or a Toolchange Macro.\n" - "The FlatCAM variables are surrounded by '%' symbol.\n\n" - "WARNING: it can be used only with a postprocessor file\n" - "that has 'toolchange_custom' in it's name and this is built\n" - "having as template the 'Toolchange Custom' posprocessor file." - ) - ) - self.cnc_box.addWidget(self.toolchangelabel) - - self.toolchange_text = FCTextArea() - self.cnc_box.addWidget(self.toolchange_text) - - cnclay = QtWidgets.QHBoxLayout() - self.cnc_box.addLayout(cnclay) - - # Toolchange Replacement Enable - self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro')) - self.toolchange_cb.setToolTip( - _("Check this box if you want to use\n" - "a Custom Toolchange GCode (macro).") - ) - - # Variable list - self.tc_variable_combo = FCComboBox() - self.tc_variable_combo.setToolTip( - _( - "A list of the FlatCAM variables that can be used\n" - "in the Toolchange event.\n" - "They have to be surrounded by the '%' symbol" - ) - ) - - # Populate the Combo Box - variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange', - 'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime'] - self.tc_variable_combo.addItems(variables) - self.tc_variable_combo.setItemData(0, _("FlatCAM CNC parameters"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(1, _("tool = tool number"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(2, _("tooldia = tool diameter"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(3, _("t_drills = for Excellon, total number of drills"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(4, _("x_toolchange = X coord for Toolchange"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(5, _("y_toolchange = Y coord for Toolchange"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(6, _("z_toolchange = Z coord for Toolchange"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(7, _("z_cut = depth where to cut"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(8, _("z_move = height where to travel"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(9, _("z_depthpercut = the step value for multidepth cut"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(10, _("spindlesspeed = the value for the spindle speed"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(11, _("dwelltime = time to dwell to allow the " - "spindle to reach it's set RPM"), - Qt.ToolTipRole) - - cnclay.addWidget(self.toolchange_cb) - cnclay.addStretch() - cnclay.addWidget(self.tc_variable_combo) - - self.toolch_ois = OptionalInputSection(self.toolchange_cb, - [self.toolchangelabel, self.toolchange_text, self.tc_variable_combo]) - h_lay = QtWidgets.QHBoxLayout() h_lay.setAlignment(QtCore.Qt.AlignVCenter) self.custom_box.addLayout(h_lay) - # Edit GCode Button - self.modify_gcode_button = QtWidgets.QPushButton(_('View CNC Code')) - self.modify_gcode_button.setToolTip( - _("Opens TAB to view/modify/print G-Code\n" - "file.") + self.autocomplete_cb = FCCheckBox("%s" % _("Auto Completer")) + self.autocomplete_cb.setToolTip( + _("This selects if the auto completer is enabled in the Script Editor.") ) - - # GO Button - self.export_gcode_button = QtWidgets.QPushButton(_('Save CNC Code')) - self.export_gcode_button.setToolTip( - _("Opens dialog to save G-Code\n" - "file.") + self.autocomplete_cb.setStyleSheet( + """ + QCheckBox {font-weight: bold; color: black} + """ ) + h_lay.addWidget(self.autocomplete_cb) + h_lay.addStretch() - h_lay.addWidget(self.modify_gcode_button) - h_lay.addWidget(self.export_gcode_button) - # self.custom_box.addWidget(self.export_gcode_button) + # Plot CB - this is added only for compatibility; other FlatCAM objects expect it and the mechanism is already + # established and I don't want to changed it right now + self.plot_cb = FCCheckBox() + self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft) + self.custom_box.addWidget(self.plot_cb) + self.plot_cb.hide() -class NotesObjectUI(ObjectUI): + self.custom_box.addStretch() + + +class DocumentObjectUI(ObjectUI): """ User interface for Notes objects. """ @@ -2016,283 +1778,28 @@ class NotesObjectUI(ObjectUI): be placed in ``self.custom_box`` to preserve the layout. """ - ObjectUI.__init__(self, title=_('Notes Object'), icon_file='share/cnc32.png', parent=parent) - - # Scale and offset ans skew are not available for CNCJob objects. - # Hiding from the GUI. - for i in range(0, self.scale_grid.count()): - self.scale_grid.itemAt(i).widget().hide() - self.scale_label.hide() - self.scale_button.hide() - - for i in range(0, self.offset_grid.count()): - self.offset_grid.itemAt(i).widget().hide() - self.offset_label.hide() - self.offset_button.hide() - - # ## Plot options - self.plot_options_label = QtWidgets.QLabel("%s:" % _("Plot Options")) - self.custom_box.addWidget(self.plot_options_label) - - self.cncplot_method_label = QtWidgets.QLabel("%s:" % _("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_combo = RadioSet([ - {"label": _("All"), "value": "all"}, - {"label": _("Travel"), "value": "travel"}, - {"label": _("Cut"), "value": "cut"} - ], stretch=False) - - self.annotation_label = QtWidgets.QLabel("%s:" % _("Display Annotation")) - self.annotation_label.setToolTip( - _("This selects if to display text annotation on the plot.\n" - "When checked it will display numbers in order for each end\n" - "of a travel line.") - ) - self.annotation_cb = FCCheckBox() + ObjectUI.__init__(self, title=_('Document Object'), + icon_file='share/notes16_1.png', + parent=parent, + common=False) # ## Object name self.name_hlay = QtWidgets.QHBoxLayout() self.custom_box.addLayout(self.name_hlay) + name_label = QtWidgets.QLabel("%s:" % _("Name")) self.name_entry = FCEntry() self.name_entry.setFocusPolicy(QtCore.Qt.StrongFocus) self.name_hlay.addWidget(name_label) self.name_hlay.addWidget(self.name_entry) - self.t_distance_label = QtWidgets.QLabel("%s:" % _("Travelled dist.")) - self.t_distance_label.setToolTip( - _("This is the total travelled distance on X-Y plane.\n" - "In current units.") - ) - self.t_distance_entry = FCEntry() - self.t_distance_entry.setToolTip( - _("This is the total travelled distance on X-Y plane.\n" - "In current units.") - ) - self.units_label = QtWidgets.QLabel() - - self.t_time_label = QtWidgets.QLabel("%s:" % _("Estimated time")) - self.t_time_label.setToolTip( - _("This is the estimated time to do the routing/drilling,\n" - "without the time spent in ToolChange events.") - ) - self.t_time_entry = FCEntry() - self.t_time_entry.setToolTip( - _("This is the estimated time to do the routing/drilling,\n" - "without the time spent in ToolChange events.") - ) - self.units_time_label = QtWidgets.QLabel() - - f_lay = QtWidgets.QGridLayout() - f_lay.setColumnStretch(1, 1) - f_lay.setColumnStretch(2, 1) - - self.custom_box.addLayout(f_lay) - 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) - f_lay.addWidget(self.annotation_label, 1, 0) - f_lay.addWidget(self.annotation_cb, 1, 1) - f_lay.addWidget(QtWidgets.QLabel(''), 1, 2) - f_lay.addWidget(self.t_distance_label, 2, 0) - f_lay.addWidget(self.t_distance_entry, 2, 1) - f_lay.addWidget(self.units_label, 2, 2) - f_lay.addWidget(self.t_time_label, 3, 0) - f_lay.addWidget(self.t_time_entry, 3, 1) - f_lay.addWidget(self.units_time_label, 3, 2) - - self.t_distance_label.hide() - self.t_distance_entry.setVisible(False) - self.t_time_label.hide() - self.t_time_entry.setVisible(False) - - e1_lbl = QtWidgets.QLabel('') - self.custom_box.addWidget(e1_lbl) - - hlay = QtWidgets.QHBoxLayout() - self.custom_box.addLayout(hlay) - - # CNC Tools Table for plot - self.cnc_tools_table_label = QtWidgets.QLabel('%s' % _('CNC Tools Table')) - self.cnc_tools_table_label.setToolTip( - _( - "Tools in this CNCJob object used for cutting.\n" - "The tool diameter is used for plotting on canvas.\n" - "The 'Offset' entry will set an offset for the cut.\n" - "'Offset' can be inside, outside, on path (none) and custom.\n" - "'Type' entry is only informative and it allow to know the \n" - "intent of using the current tool. \n" - "It can be Rough(ing), Finish(ing) or Iso(lation).\n" - "The 'Tool type'(TT) can be circular with 1 to 4 teeths(C1..C4),\n" - "ball(B), or V-Shaped(V)." - ) - ) - hlay.addWidget(self.cnc_tools_table_label) - - # Plot CB - # self.plot_cb = QtWidgets.QCheckBox('Plot') - self.plot_cb = FCCheckBox(_('Plot Object')) - self.plot_cb.setToolTip( - _("Plot (show) this object.") - ) + # Plot CB - this is added only for compatibility; other FlatCAM objects expect it and the mechanism is already + # established and I don't want to changed it right now + self.plot_cb = FCCheckBox() self.plot_cb.setLayoutDirection(QtCore.Qt.RightToLeft) - hlay.addStretch() - hlay.addWidget(self.plot_cb) + self.custom_box.addWidget(self.plot_cb) + self.plot_cb.hide() - self.cnc_tools_table = FCTable() - self.custom_box.addWidget(self.cnc_tools_table) - - # self.cnc_tools_table.setColumnCount(4) - # self.cnc_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Plot', '']) - # self.cnc_tools_table.setColumnHidden(3, True) - self.cnc_tools_table.setColumnCount(7) - self.cnc_tools_table.setColumnWidth(0, 20) - self.cnc_tools_table.setHorizontalHeaderLabels(['#', _('Dia'), _('Offset'), _('Type'), _('TT'), '', - _('P')]) - self.cnc_tools_table.setColumnHidden(5, True) - # stylesheet = "::section{Background-color:rgb(239,239,245)}" - # self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet) - - # Update plot button - self.updateplot_button = QtWidgets.QPushButton(_('Update Plot')) - self.updateplot_button.setToolTip( - _("Update the plot.") - ) - self.custom_box.addWidget(self.updateplot_button) - - # #################### - # ## Export G-Code ## - # #################### - self.export_gcode_label = QtWidgets.QLabel("%s:" % _("Export CNC Code")) - self.export_gcode_label.setToolTip( - _("Export and save G-Code to\n" - "make this object to a file.") - ) - self.custom_box.addWidget(self.export_gcode_label) - - # Prepend text to GCode - prependlabel = QtWidgets.QLabel('%s:' % _('Prepend to CNC Code')) - prependlabel.setToolTip( - _("Type here any G-Code commands you would\n" - "like to add at the beginning of the G-Code file.") - ) - self.custom_box.addWidget(prependlabel) - - self.prepend_text = FCTextArea() - self.custom_box.addWidget(self.prepend_text) - - # Append text to GCode - appendlabel = QtWidgets.QLabel('%s:' % _('Append to CNC Code')) - appendlabel.setToolTip( - _("Type here any G-Code commands you would\n" - "like to append to the generated file.\n" - "I.e.: M2 (End of program)") - ) - self.custom_box.addWidget(appendlabel) - - self.append_text = FCTextArea() - self.custom_box.addWidget(self.append_text) - - self.cnc_frame = QtWidgets.QFrame() - self.cnc_frame.setContentsMargins(0, 0, 0, 0) - self.custom_box.addWidget(self.cnc_frame) - self.cnc_box = QtWidgets.QVBoxLayout() - self.cnc_box.setContentsMargins(0, 0, 0, 0) - self.cnc_frame.setLayout(self.cnc_box) - - # Toolchange Custom G-Code - self.toolchangelabel = QtWidgets.QLabel('%s:' % _('Toolchange G-Code')) - self.toolchangelabel.setToolTip( - _( - "Type here any G-Code commands you would\n" - "like to be executed when Toolchange event is encountered.\n" - "This will constitute a Custom Toolchange GCode,\n" - "or a Toolchange Macro.\n" - "The FlatCAM variables are surrounded by '%' symbol.\n\n" - "WARNING: it can be used only with a postprocessor file\n" - "that has 'toolchange_custom' in it's name and this is built\n" - "having as template the 'Toolchange Custom' posprocessor file." - ) - ) - self.cnc_box.addWidget(self.toolchangelabel) - - self.toolchange_text = FCTextArea() - self.cnc_box.addWidget(self.toolchange_text) - - cnclay = QtWidgets.QHBoxLayout() - self.cnc_box.addLayout(cnclay) - - # Toolchange Replacement Enable - self.toolchange_cb = FCCheckBox(label='%s' % _('Use Toolchange Macro')) - self.toolchange_cb.setToolTip( - _("Check this box if you want to use\n" - "a Custom Toolchange GCode (macro).") - ) - - # Variable list - self.tc_variable_combo = FCComboBox() - self.tc_variable_combo.setToolTip( - _( - "A list of the FlatCAM variables that can be used\n" - "in the Toolchange event.\n" - "They have to be surrounded by the '%' symbol" - ) - ) - - # Populate the Combo Box - variables = [_('Parameters'), 'tool', 'tooldia', 't_drills', 'x_toolchange', 'y_toolchange', 'z_toolchange', - 'z_cut', 'z_move', 'z_depthpercut', 'spindlespeed', 'dwelltime'] - self.tc_variable_combo.addItems(variables) - self.tc_variable_combo.setItemData(0, _("FlatCAM CNC parameters"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(1, _("tool = tool number"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(2, _("tooldia = tool diameter"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(3, _("t_drills = for Excellon, total number of drills"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(4, _("x_toolchange = X coord for Toolchange"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(5, _("y_toolchange = Y coord for Toolchange"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(6, _("z_toolchange = Z coord for Toolchange"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(7, _("z_cut = depth where to cut"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(8, _("z_move = height where to travel"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(9, _("z_depthpercut = the step value for multidepth cut"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(10, _("spindlesspeed = the value for the spindle speed"), Qt.ToolTipRole) - self.tc_variable_combo.setItemData(11, _("dwelltime = time to dwell to allow the " - "spindle to reach it's set RPM"), - Qt.ToolTipRole) - - cnclay.addWidget(self.toolchange_cb) - cnclay.addStretch() - cnclay.addWidget(self.tc_variable_combo) - - self.toolch_ois = OptionalInputSection(self.toolchange_cb, - [self.toolchangelabel, self.toolchange_text, self.tc_variable_combo]) - - h_lay = QtWidgets.QHBoxLayout() - h_lay.setAlignment(QtCore.Qt.AlignVCenter) - self.custom_box.addLayout(h_lay) - - # Edit GCode Button - self.modify_gcode_button = QtWidgets.QPushButton(_('View CNC Code')) - self.modify_gcode_button.setToolTip( - _("Opens TAB to view/modify/print G-Code\n" - "file.") - ) - - # GO Button - self.export_gcode_button = QtWidgets.QPushButton(_('Save CNC Code')) - self.export_gcode_button.setToolTip( - _("Opens dialog to save G-Code\n" - "file.") - ) - - h_lay.addWidget(self.modify_gcode_button) - h_lay.addWidget(self.export_gcode_button) - # self.custom_box.addWidget(self.export_gcode_button) + self.custom_box.addStretch() # end of file diff --git a/flatcamTools/ToolRulesCheck.py b/flatcamTools/ToolRulesCheck.py index fa5d5d0b..26f7c26d 100644 --- a/flatcamTools/ToolRulesCheck.py +++ b/flatcamTools/ToolRulesCheck.py @@ -486,7 +486,7 @@ class RulesCheck(FlatCAMTool): self.constrain_flag = False # Multiprocessing Process Pool - self.pool = Pool(processes=cpu_count()) + self.pool = self.app.pool self.results = None # def on_object_loaded(self, index, row): diff --git a/tclCommands/TclCommandCncjob.py b/tclCommands/TclCommandCncjob.py index 28deb94b..74778c26 100644 --- a/tclCommands/TclCommandCncjob.py +++ b/tclCommands/TclCommandCncjob.py @@ -47,7 +47,7 @@ class TclCommandCncjob(TclCommandSignaled): ]) # array of mandatory options for current Tcl command: required = {'name','outname'} - required = ['name'] + required = [] # structured help for current command, args needs to be ordered help = { @@ -88,16 +88,24 @@ class TclCommandCncjob(TclCommandSignaled): :return: None or exception """ - name = args['name'] - - if 'outname' not in args: - args['outname'] = str(name) + "_cnc" + name = '' if 'muted' in args: muted = args['muted'] else: muted = 0 + try: + name = args['name'] + except KeyError: + if muted == 0: + self.raise_tcl_error("Object name is missing") + else: + return "fail" + + if 'outname' not in args: + args['outname'] = str(name) + "_cnc" + obj = self.app.collection.get_by_name(str(name), isCaseSensitive=False) if obj is None: