From 3d4e195ae1c752f6087dfd0d2a1c4b97e9f5b276 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 10 Feb 2022 14:01:32 +0200 Subject: [PATCH] - added a new preprocessor: `default_laser` - in the app added a new tool type: "L" (laser) which when selected will autoselect the `default_laser` preprocessor in Milling Plugin --- CHANGELOG.md | 5 + appDatabase.py | 9 +- appEditors/appGCodeEditor.py | 2 +- appGUI/ObjectUI.py | 4 +- .../preferences/tools/ToolsISOPrefGroupUI.py | 5 +- appObjects/FlatCAMCNCJob.py | 2 +- appObjects/FlatCAMGeometry.py | 2 +- appPlugins/ToolIsolation.py | 9 +- appPlugins/ToolMilling.py | 15 +- appPlugins/ToolNCC.py | 5 +- appPlugins/ToolPaint.py | 5 +- preprocessors/default.py | 2 +- preprocessors/default_laser.py | 130 ++++++++++++++++++ 13 files changed, 173 insertions(+), 22 deletions(-) create mode 100644 preprocessors/default_laser.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cb7d2470..7161aca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM beta ================================================= +10.02.2022 + +- added a new preprocessor: `default_laser` +- in the app added a new tool type: "L" (laser) which when selected will autoselect the `default_laser` preprocessor in Milling Plugin + 7.02.2022 - fixed some issues when exporting Excellon objects diff --git a/appDatabase.py b/appDatabase.py index 0562e4a2..311ca72a 100644 --- a/appDatabase.py +++ b/appDatabase.py @@ -4,7 +4,7 @@ from appGUI.GUIElements import FCEntry, FCButton, FCDoubleSpinner, FCComboBox, F from camlib import to_dict import sys -import json +import simplejson as json from copy import deepcopy from datetime import datetime @@ -27,7 +27,7 @@ class ToolsDB2UI: self.offset_item_options = [_("Path"), _("In"), _("Out"), _("Custom")] self.job_item_options = [_('Roughing'), _('Finishing'), _('Isolation'), _('Polishing')] - self.tool_job_options = ["C1", "C2", "C3", "C4", "B", "V"] + self.tool_job_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] self.g_lay = grid_layout @@ -288,7 +288,8 @@ class ToolsDB2UI: "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool") + "V = v-shape milling tool\n" + "L = laser") ) self.mill_shape_combo = FCComboBox() @@ -2710,7 +2711,7 @@ class ToolsDB2(QtWidgets.QWidget): # # self.offset_item_options = ["Path", "In", "Out", "Custom"] # self.type_item_options = ["Iso", "Rough", "Finish"] -# self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] +# self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] # # ''' # dict to hold all the tools in the Tools DB diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py index 68ae7d80..9f133d04 100644 --- a/appEditors/appGCodeEditor.py +++ b/appEditors/appGCodeEditor.py @@ -197,7 +197,7 @@ class AppGCodeEditor(QtCore.QObject): job_item = QtWidgets.QTableWidgetItem(job_item_txt) # -------------------- TOOL SHAPE ------------------------------------- # - tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] try: tool_shape_item_txt = tool_type_item_options[dia_value['data']['tools_mill_tool_shape']] except TypeError: diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index 739a9409..95020e8f 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -1093,7 +1093,9 @@ class GeometryObjectUI(ObjectUI): "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool")) + "V = v-shape milling tool\n" + "L = laser") + ) self.geo_tools_table.horizontalHeaderItem(6).setToolTip( _("Plot column. It is visible only for MultiGeo Geometry objects.\n" "Enable plot for the selected tool geometry.")) diff --git a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py index c4832ff1..0937d1e2 100644 --- a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py @@ -151,11 +151,12 @@ class ToolsISOPrefGroupUI(OptionsGroupUI): "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool") + "V = v-shape milling tool\n" + "L = laser") ) self.tool_shape_combo = FCComboBox2(policy=False) - self.tool_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"]) + self.tool_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V", "L"]) tool_grid.addWidget(tool_shape_label, 0, 0) tool_grid.addWidget(self.tool_shape_combo, 0, 1, 1, 2) diff --git a/appObjects/FlatCAMCNCJob.py b/appObjects/FlatCAMCNCJob.py index a4ccd931..a0e8cfea 100644 --- a/appObjects/FlatCAMCNCJob.py +++ b/appObjects/FlatCAMCNCJob.py @@ -237,7 +237,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): offset_item = QtWidgets.QTableWidgetItem(''.join(offset_txt)) job_item_options = [_('Roughing'), _('Finishing'), _('Isolation'), _('Polishing')] - tool_shape_options = ["C1", "C2", "C3", "C4", "B", "V"] + tool_shape_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] try: job_item_txt = job_item_options[dia_value['data']['tools_mill_job_type']] diff --git a/appObjects/FlatCAMGeometry.py b/appObjects/FlatCAMGeometry.py index fb37c41d..2ecf71af 100644 --- a/appObjects/FlatCAMGeometry.py +++ b/appObjects/FlatCAMGeometry.py @@ -123,7 +123,7 @@ class GeometryObject(FlatCAMObj, Geometry): self.offset_item_options = ["Path", "In", "Out", "Custom"] self.job_item_options = [_('Roughing'), _('Finishing'), _('Isolation'), _('Polishing')] - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] # flag to store if the V-Shape tool is selected in self.ui.geo_tools_table self.v_tool_type = None diff --git a/appPlugins/ToolIsolation.py b/appPlugins/ToolIsolation.py index f7241c07..943a7fb8 100644 --- a/appPlugins/ToolIsolation.py +++ b/appPlugins/ToolIsolation.py @@ -444,7 +444,7 @@ class ToolIsolation(AppTool, Gerber): self.sel_rect = [] - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] self.on_rest_machining_check(state=self.app.defaults["tools_iso_rest"]) @@ -1754,7 +1754,7 @@ class ToolIsolation(AppTool, Gerber): use_rest = args['rest'] if 'rest' in args else self.ui.rest_cb.get_value() # selection_type: [_("All"), _("Area Selection"), _("Polygon Selection"), _("Reference Object")] selection_type = args['sel_type'] if 'sel_type' in args else self.ui.select_combo.get_value() - # tool_tip_shape: ["C1", "C2", "C3", "C4", "B", "V"] + # tool_tip_shape: ["C1", "C2", "C3", "C4", "B", "V", "L"] tool_tip_shape = args['tip_shape'] if 'tip_shape' in args else self.ui.tool_shape_combo.get_value() # update the Common Parameters values in the self.iso_tools @@ -3544,12 +3544,13 @@ class IsoUI: "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool") + "V = v-shape milling tool\n" + "L = laser") ) self.tool_shape_combo = FCComboBox2(policy=False) self.tool_shape_combo.setObjectName('i_tool_shape') - self.tool_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"]) + self.tool_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V", "L"]) idx = int(self.app.defaults['tools_iso_tool_shape']) # protection against having this translated or loading a project with translated values diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index 64489c8d..a5f0338c 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -1018,7 +1018,7 @@ class ToolMilling(AppTool, Excellon): # -------------------- TOOL TYPE ------------------------------------- # # tool_type_item = FCComboBox(policy=False) - # for item in ["C1", "C2", "C3", "C4", "B", "V"]: + # for item in ["C1", "C2", "C3", "C4", "B", "V", "L"]: # tool_type_item.addItem(item) # idx = tool_type_item.findText(tooluid_value['data']['tools_mill_tool_type']) # # protection against having this translated or loading a project with translated values @@ -1943,9 +1943,17 @@ class ToolMilling(AppTool, Excellon): tool_type = cw.currentText() self.ui_update_v_shape(tool_type) + self.ui_update_l_shape(tool_type) self.form_to_storage() + def ui_update_l_shape(self, t_type): + if t_type.upper() == 'L': + self.ui.pp_geo_name_cb.set_value('default_laser') + else: + self.ui.pp_geo_name_cb.set_value(self.app.defaults['tools_mill_ppname_g']) + self.on_pp_changed() + def ui_update_v_shape(self, tool_type_txt): if tool_type_txt == 'V': self.ui.tipdialabel.show() @@ -4403,12 +4411,13 @@ class MillingUI: "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool") + "V = v-shape milling tool\n" + "L = laser") ) self.tool_shape_combo = FCComboBox2(policy=False) self.tool_shape_combo.setObjectName('mill_tool_shape') - self.tool_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"]) + self.tool_shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V", "L"]) idx = int(self.app.defaults['tools_mill_tool_shape']) # protection against having this translated or loading a project with translated values diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index febfa941..d96be80a 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -388,7 +388,7 @@ class NonCopperClear(AppTool, Gerber): self.bound_obj_name = "" self.bound_obj = None - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] self.units = self.app.app_units.upper() self.first_click = False @@ -4180,7 +4180,8 @@ class NccUI: "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool")) + "V = v-shape milling tool\n" + "L = laser")) # Tool order self.ncc_order_label = FCLabel('%s:' % _('Tool order')) diff --git a/appPlugins/ToolPaint.py b/appPlugins/ToolPaint.py index 5c94ad15..3dcb1a91 100644 --- a/appPlugins/ToolPaint.py +++ b/appPlugins/ToolPaint.py @@ -103,7 +103,7 @@ class ToolPaint(AppTool, Gerber): # store here the default data for Geometry Data self.default_data = {} - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V", "L"] self.form_fields = { "tools_paint_overlap": self.ui.paintoverlap_entry, @@ -3056,7 +3056,8 @@ class PaintUI: "Can be:\n" "C1 ... C4 = circular tool with x flutes\n" "B = ball tip milling tool\n" - "V = v-shape milling tool")) + "V = v-shape milling tool\n" + "L = laser")) # Tool Order self.order_label = FCLabel('%s:' % _('Tool order')) diff --git a/preprocessors/default.py b/preprocessors/default.py index d9d060bd..97b7ef10 100644 --- a/preprocessors/default.py +++ b/preprocessors/default.py @@ -19,7 +19,7 @@ class default(PreProc): units = ' ' + str(p['units']).lower() coords_xy = p['xy_toolchange'] end_coords_xy = p['xy_end'] - gcode = '(This preprocessor is the default preprocessor used by FlatCAM.)\n' + gcode = '(This preprocessor is the default preprocessor.)\n' gcode += '(It is made to work with MACH3 compatible motion controllers.)\n\n' xmin = '%.*f' % (p.coords_decimals, p['options']['xmin']) diff --git a/preprocessors/default_laser.py b/preprocessors/default_laser.py new file mode 100644 index 00000000..4abb8861 --- /dev/null +++ b/preprocessors/default_laser.py @@ -0,0 +1,130 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# File Author: Matthieu Berthomé # +# Date: 5/26/2017 # +# MIT Licence # +# ########################################################## + +from appPreProcessor import * + + +class default_laser(PreProc): + + include_header = True + coordinate_format = "%.*f" + feedrate_format = '%.*f' + + def start_code(self, p): + units = ' ' + str(p['units']).lower() + + gcode = '(This preprocessor is the default preprocessor when used with a laser.)\n' + gcode += '(It is made to work with MACH3 compatible motion controllers.)\n' \ + '(This preprocessor makes no moves on the Z axis it will only move horizontally.)\n' \ + '(The horizontal move is done with G0 - highest possible speed set in MACH3.)\n' \ + '(It assumes a manually focused laser.)\n' \ + '(The laser is started with M3 command and stopped with the M5 command.)\n\n' + + xmin = '%.*f' % (p.coords_decimals, p['options']['xmin']) + xmax = '%.*f' % (p.coords_decimals, p['options']['xmax']) + ymin = '%.*f' % (p.coords_decimals, p['options']['ymin']) + ymax = '%.*f' % (p.coords_decimals, p['options']['ymax']) + + gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n' + gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' + + if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + gcode += '(Preprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + else: + gcode += '(Preprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n' + + gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n' + gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n' + + gcode += '(Laser Power - Spindle Speed: ' + str(p['spindlespeed']) + ')\n' + gcode += '(Laser Minimum Power: ' + str(p['laser_min_power']) + ')\n\n' + + gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n') + gcode += 'G90\n' + gcode += 'G94' + + return gcode + + def startz_code(self, p): + return '' + + def lift_code(self, p): + if float(p.laser_min_power) > 0.0: + # the formatted text: laser OFF must always be like this else the plotting will not be done correctly + return 'M03 S%s (laser OFF)\n' % str(p.laser_min_power) + else: + return 'M05' + + def down_code(self, p): + if p.spindlespeed: + return '%s S%s' % ('M03', str(p.spindlespeed)) + else: + return 'M03' + + def toolchange_code(self, p): + return '' + + def up_to_zero_code(self, p): + return '' + + def position_code(self, p): + # used in for the linear motion + # formula for skewing on x for example is: + # x_fin = x_init + y_init/slope where slope = p._bed_limit_y / p._bed_skew_x (a.k.a tangent) + if p._bed_skew_x == 0: + x_pos = p.x + p._bed_offset_x + else: + x_pos = (p.x + p._bed_offset_x) + ((p.y / p._bed_limit_y) * p._bed_skew_x) + + if p._bed_skew_y == 0: + y_pos = p.y + p._bed_offset_y + else: + y_pos = (p.y + p._bed_offset_y) + ((p.x / p._bed_limit_x) * p._bed_skew_y) + + return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \ + (p.coords_decimals, x_pos, p.coords_decimals, y_pos) + + def rapid_code(self, p): + # a fast linear motion using the G0 command which means: "move as fast as the CNC can handle and is set in the + # CNC controller". It is a horizontal move in the X-Y CNC plane. + return ('G00 ' + self.position_code(p)).format(**p) + + def linear_code(self, p): + # a linear motion using the G1 command which means: "move with a set feedrate (speed)". + # It is a horizontal move in the X-Y CNC plane. + return ('G01 ' + self.position_code(p)).format(**p) + + def end_code(self, p): + # a final move at the end of the CNC job. First it moves to a safe parking Z height followed by an X-Y move + # to the parking location. + end_coords_xy = p['xy_end'] + gcode = '' + + if end_coords_xy and end_coords_xy != '': + gcode += 'G00 X{x} Y{y}'.format(x=end_coords_xy[0], y=end_coords_xy[1]) + "\n" + return gcode + + def feedrate_code(self, p): + # set the feedrate for the linear move with G1 command on the X-Y CNC plane (horizontal move) + return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.feedrate)) + + def z_feedrate_code(self, p): + # set the feedrate for the linear move with G1 command on the Z CNC plane (vertical move) + return 'G01 F' + str(self.feedrate_format % (p.fr_decimals, p.z_feedrate)) + + def spindle_code(self, p): + if p.spindlespeed: + return '%s S%s' % ('M03', str(p.spindlespeed)) + else: + return 'M03' + + def dwell_code(self, p): + return '' + + def spindle_stop_code(self, p): + return 'M05'