Merge branch 'Beta'

This commit is contained in:
David Robertson 2020-05-03 01:53:27 +01:00
commit 1fdd0c26e0
52 changed files with 58356 additions and 48641 deletions

View File

@ -7,6 +7,27 @@ CHANGELOG for FlatCAM beta
=================================================
2.05.2020
- changed the icons for the grid snap in the status bar
- moved some of the methods from FlatCAMApp.App to flatcamGUI.FlatCAMGUI class
- fixed bug in Gerber Editor in which the units conversion wasn't calculated correct
- fixed bug in Gerber Editor in which the QThread that is started on object edit was not stopped at clean up stage
- fixed bug in Gerber Editor that kept all the apertures (including the geometry) of a previously edited object that was not saved after edit
- modified the Cutout Tool to generate multi-geo objects therefore the set geometry parameters will populate the Geometry Object UI
- modified the Panelize Tool to optimize the output from Cutout Tool such that there are no longer overlapping cuts
- some string corrections
- updated the Italian translation done by user @pcb-hobbyst
- RELEASE 8.992
01.05.2020
- added some ToolTips (strings needed to be translated too) for the Cut Z entry in Geometry Object UI that explain why is sometime disabled and reason for it's value (sometime is zero)
- solve parenting issues when trying to load a FlatScript from Menu -> File -> Scripting
- added a first new example script and added some files to work with
- added a new parameter that will store the home folder of the FlatCAM installation so we can access the example folder
- added in Gerber editor a method for zoom fit that takes into consideration the current geometry of the edited object
30.04.2020
- made some corrections - due of recent refactoring PyCharm reported errors all over (not correct but it made programming difficult)

View File

@ -163,7 +163,7 @@ class App(QtCore.QObject):
# ################################### Version and VERSION DATE ##################################################
# ###############################################################################################################
version = 8.992
version_date = "2020/05/01"
version_date = "2020/05/03"
beta = True
engine = '3D'
@ -432,6 +432,9 @@ class App(QtCore.QObject):
# ################################# DEFAULTS - PREFERENCES STORAGE ###########################################
# ############################################################################################################
self.defaults = FlatCAMDefaults()
self.defaults["root_folder_path"] = self.app_home
current_defaults_path = os.path.join(self.data_path, "current_defaults.FlatConfig")
if user_defaults:
self.defaults.load(filename=current_defaults_path)
@ -503,7 +506,6 @@ class App(QtCore.QObject):
QtCore.QObject.__init__(self)
self.ui = FlatCAMGUI(self)
self.on_grid_snap_triggered(state=True)
theme_settings = QtCore.QSettings("Open Source", "FlatCAM")
if theme_settings.contains("theme"):
@ -1071,9 +1073,6 @@ class App(QtCore.QObject):
# signal emitted when a tab is closed in the Plot Area
self.ui.plot_tab_area.tab_closed_signal.connect(self.on_plot_area_tab_closed)
self.ui.grid_snap_btn.triggered.connect(self.on_grid_snap_triggered)
self.ui.snap_infobar_label.clicked.connect(self.on_grid_icon_snap_clicked)
# signal to close the application
self.close_app_signal.connect(self.kill_app)
# ################################# FINISHED CONNECTING SIGNALS #############################################
@ -2908,45 +2907,37 @@ class App(QtCore.QObject):
self.new_object('gerber', 'new_grb', initialize, plot=False)
def new_script_object(self, name=None, text=None):
def new_script_object(self):
"""
Creates a new, blank TCL Script object.
:param name: a name for the new object
:param text: pass a source file to the newly created script to be loaded in it
:return: None
"""
self.defaults.report_usage("new_script_object()")
if text is not None:
new_source_file = text
else:
# commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
# "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
# "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
# "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
# "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
# "ListSys, MillDrills,\n" \
# "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
# "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
# "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
# "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
# "# SubtractRectangle, Version, WriteGCode\n"
# commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \
# "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \
# "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \
# "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \
# "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \
# "ListSys, MillDrills,\n" \
# "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \
# "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \
# "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \
# "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \
# "# SubtractRectangle, Version, WriteGCode\n"
new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
'# %s:\n' % _('TCL Tutorial is here') + \
'# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
'# %s:\n' % _("FlatCAM commands list")
new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
"(displayed in Tcl Shell).")
new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \
'# %s:\n' % _('TCL Tutorial is here') + \
'# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \
'# %s:\n' % _("FlatCAM commands list")
new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands "
"(displayed in Tcl Shell).")
def initialize(obj, app):
obj.source_file = deepcopy(new_source_file)
if name is None:
outname = 'new_script'
else:
outname = name
outname = 'new_script'
self.new_object('script', outname, initialize, plot=False)
def new_document_object(self):
@ -3268,7 +3259,7 @@ class App(QtCore.QObject):
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 4, 2)
self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 5, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Alex Lazar"), 6, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "David Robertson"), 6, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Matthieu Berthomé"), 7, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Mike Evans"), 8, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Victor Benso"), 9, 0)
@ -3300,6 +3291,7 @@ class App(QtCore.QObject):
self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 63, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Alex Lazar"), 64, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Breneman"), 65, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Eric Varsanyi"), 67, 0)
self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Lubos Medovarsky"), 69, 0)
@ -3336,27 +3328,43 @@ class App(QtCore.QObject):
self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Translator")), 0, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("Corrections")), 0, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('<b>%s</b>' % _("E-mail")), 0, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "BR - Portuguese"), 1, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Carlos Stein"), 1, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<carlos.stein@gmail.com>"), 1, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "French"), 2, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 2, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 2, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 2, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 3, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 3, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt, Detlef Eckardt"), 3, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Hungarian"), 3, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 3, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 4, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 4, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Russian"), 5, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 5, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<camellan@yandex.ru>"), 5, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Spanish"), 6, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 6, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 6, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 6, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Italian"), 4, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Golfetto Massimiliano"), 4, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 4, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "pcb@golfetto.eu"), 4, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "German"), 5, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 5, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jens Karstedt, Detlef Eckardt"), 5, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 5, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Romanian"), 6, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 6, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<marius_adrian@yahoo.com>"), 6, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Russian"), 7, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 7, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<camellan@yandex.ru>"), 7, 3)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Spanish"), 8, 0)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu (Google-Tr)"), 8, 1)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % ""), 8, 2)
self.translator_grid_lay.addWidget(QtWidgets.QLabel('%s' % " "), 8, 3)
self.translator_grid_lay.setColumnStretch(0, 0)
self.translators_tab_layout.addStretch()
@ -4120,9 +4128,11 @@ class App(QtCore.QObject):
obj.multigeo = True
for tooluid, dict_value in obj.tools.items():
dict_value['solid_geometry'] = deepcopy(obj.solid_geometry)
if not isinstance(obj.solid_geometry, list):
obj.solid_geometry = [obj.solid_geometry]
obj.solid_geometry[:] = []
# obj.solid_geometry[:] = []
obj.plot()
self.should_we_save = True
@ -4639,7 +4649,7 @@ class App(QtCore.QObject):
self.defaults.report_usage("on_toggle_grid()")
self.ui.grid_snap_btn.trigger()
self.on_grid_snap_triggered(state=True)
self.ui.on_grid_snap_triggered(state=True)
def on_toggle_grid_lines(self):
self.defaults.report_usage("on_toggle_grd_lines()")
@ -5033,7 +5043,7 @@ class App(QtCore.QObject):
self.paste_tool.on_add_tool_by_key()
# It's meant to delete tools in tool tables via a 'Delete' shortcut key but only if certain conditions are met
# See description bellow.
# See description below.
def on_delete_keypress(self):
notebook_widget_name = self.ui.notebook.currentWidget().objectName()
@ -6949,7 +6959,6 @@ class App(QtCore.QObject):
else:
key_modifier = QtWidgets.QApplication.keyboardModifiers()
if key_modifier == QtCore.Qt.ShiftModifier:
mod_key = 'Shift'
elif key_modifier == QtCore.Qt.ControlModifier:
@ -6958,20 +6967,19 @@ class App(QtCore.QObject):
mod_key = None
try:
if mod_key == self.defaults["global_mselect_key"]:
if self.command_active is None:
# If the CTRL key is pressed when the LMB is clicked then if the object is selected it will
# deselect, and if it's not selected then it will be selected
# If there is no active command (self.command_active is None) then we check if we clicked
# on a object by checking the bounding limits against mouse click position
if self.command_active is None:
if mod_key == self.defaults["global_mselect_key"]:
self.select_objects(key='multisel')
self.delete_hover_shape()
else:
# If there is no active command (self.command_active is None) then we check if we clicked
# on a object by checking the bounding limits against mouse click position
if self.command_active is None:
else:
# If there is no active command (self.command_active is None) then we check if
# we clicked on a object by checking the bounding limits against mouse click position
self.select_objects()
self.delete_hover_shape()
self.delete_hover_shape()
except Exception as e:
log.warning("FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e))
return
@ -8167,7 +8175,6 @@ class App(QtCore.QObject):
# ###############################################################################################################
# ### The following section has the functions that are displayed and call the Editor tab CNCJob Tab #############
# ###############################################################################################################
def init_code_editor(self, name):
self.text_editor_tab = TextEditor(app=self, plain_text=True)
@ -8339,14 +8346,14 @@ class App(QtCore.QObject):
# set cursor of the code editor with the cursor at the searcehd line
self.ui.plot_tab_area.currentWidget().code_editor.setTextCursor(cursor)
def on_filenewscript(self, silent=False, name=None, text=None):
def on_filenewscript(self, silent=False):
"""
Will create a new script file and open it in the Code Editor
:param silent: if True will not display status messages
:param name: if specified will be the name of the new script
:param text: pass a source file to the newly created script to be loaded in it
:return: None
:param silent: if True will not display status messages
:param name: if specified will be the name of the new script
:param text: pass a source file to the newly created script to be loaded in it
:return: None
"""
if silent is False:
self.inform.emit('[success] %s' % _("New TCL script file created in Code Editor."))
@ -8355,10 +8362,7 @@ class App(QtCore.QObject):
self.ui.position_label.setText("")
self.ui.rel_position_label.setText("")
if name is not None:
self.new_script_object(name=name, text=text)
else:
self.new_script_object(text=text)
self.new_script_object()
# script_text = script_obj.source_file
#
@ -8372,9 +8376,9 @@ class App(QtCore.QObject):
"""
Will open a Tcl script file into the Code Editor
:param silent: if True will not display status messages
:param name: name of a Tcl script file to open
:return:
:param silent: if True will not display status messages
:param name: name of a Tcl script file to open
:return: None
"""
self.defaults.report_usage("on_fileopenscript")
@ -9609,27 +9613,46 @@ class App(QtCore.QObject):
:param silent: If True there will be no messages printed to StatusBar
:return: None
"""
def obj_init(script_obj, app_obj):
assert isinstance(script_obj, ScriptObject), \
"Expected to initialize a ScriptObject but got %s" % type(script_obj)
if silent is False:
app_obj.inform.emit('[success] %s' % _("TCL script file opened in Code Editor."))
try:
script_obj.parse_file(filename)
except IOError:
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename))
return "fail"
except ParseError as err:
app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(err)))
app_obj.log.error(str(err))
return "fail"
except Exception as e:
log.debug("App.open_script() -> %s" % str(e))
msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n")
msg += traceback.format_exc()
app_obj.inform.emit(msg)
return "fail"
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)
# Object creation
ret_val = self.new_object("script", script_name, obj_init, autoselected=False, plot=False)
if ret_val == 'fail':
filename = self.defaults['global_tcl_path'] + '/' + script_name
ret_val = self.new_object("script", script_name, obj_init, autoselected=False, plot=False)
if ret_val == 'fail':
self.inform.emit('[ERROR_NOTCL]%s' % _('Failed to open TCL Script.'))
return 'fail'
# Register recent file
self.file_opened.emit("script", filename)
@ -10611,29 +10634,6 @@ class App(QtCore.QObject):
update_colors=(new_color, new_line_color)
)
def on_grid_snap_triggered(self, state):
"""
:param state: A parameter with the state of the grid, boolean
:return:
"""
if state:
self.ui.snap_infobar_label.setPixmap(QtGui.QPixmap(self.resource_location + '/snap_filled_16.png'))
else:
self.ui.snap_infobar_label.setPixmap(QtGui.QPixmap(self.resource_location + '/snap_16.png'))
self.ui.snap_infobar_label.clicked_state = state
def on_grid_icon_snap_clicked(self):
"""
Slot called by clicking a GUI element, in this case a FCLabel
:return:
"""
if isinstance(self.sender(), FCLabel):
self.ui.grid_snap_btn.trigger()
def generate_cnc_job(self, objects):
"""
Slot that will be called by clicking an entry in the contextual menu generated in the Project Tab tree
@ -10836,21 +10836,6 @@ class App(QtCore.QObject):
# no_km)
# QtWidgets.qApp.sendEvent(self.shell._edit, f)
def on_toggle_shell_from_settings(self, state):
"""
Toggle shell: if is visible close it, if it is closed then open it
:return: None
"""
self.defaults.report_usage("on_toggle_shell_from_settings()")
if state is True:
if not self.ui.shell_dock.isVisible():
self.ui.shell_dock.show()
else:
if self.ui.shell_dock.isVisible():
self.ui.shell_dock.hide()
def shell_message(self, msg, show=False, error=False, warning=False, success=False, selected=False):
"""
Shows a message on the FlatCAM Shell

View File

@ -0,0 +1,69 @@
G04*
G04 GERBER (RE)GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01*
G04 Filename: test.gbr*
G04 Created on : Friday, 01 May 2020 at 17:03*
%INBottom.gbr*%
%MOMM*%
%ADD13C,0.8*%
%ADD14C,1.27*%
%ADD15C,1.27*%
%ADD16R,1.5X1.5*%
%ADD17C,1.5*%
%FSLAX53Y53*%
G04*
G71*
G90*
G75*
G01*
%LNBottom*%
%LPD*%
X20779Y24462D2*
D13*
Y27002D1*
X18229D1*
X14429D1*
X18229D2*
X19509D1*
Y28272D1*
X32209Y14302D2*
X28399D1*
Y16842D1*
Y14302D2*
X18239D1*
X20779Y16842D2*
X23329D1*
Y28272D1*
X27129D1*
X20779Y19382D2*
X14429D1*
X20779Y21922D2*
X18239D1*
X28399D2*
X32209D1*
D14*
X27129Y28272D3*
D15*
X19509D3*
D14*
X14429Y19382D3*
D15*
Y27002D3*
D14*
X18239Y21922D3*
D15*
Y14302D3*
D14*
X32209Y21922D3*
D15*
Y14302D3*
D16*
X28399Y24462D3*
D17*
Y21922D3*
Y19382D3*
Y16842D3*
X20779D3*
Y19382D3*
Y21922D3*
Y24462D3*
M02*

View File

@ -0,0 +1,28 @@
M48
;EXCELLON GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01
;Filename: test.txt
;Created on : Friday, 01 May 2020 at 17:04
INCH,LZ
;FILE_FORMAT=2:4
T1F00S00C0.0220
T2F00S00C0.0354
%
T01
X010681Y011130
X007681Y011130
X005681Y010630
X007181Y008630
X005681Y007630
X007181Y005630
X012681Y005630
X012681Y008630
T02
X011181Y009630
X011181Y008630
X011181Y007630
X011181Y006630
X008181Y006630
X008181Y007630
X008181Y008630
X008181Y009630
M30

View File

@ -0,0 +1,30 @@
G04*
G04 GERBER (RE)GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01*
G04 Filename: test_1*
G04 Created on : Friday, 01 May 2020 at 17:07*
G04*
G04 RS-274X GERBER GENERATED BY FLATCAM v8.992 - www.flatcam.org - Version Date: 2020/05/01*
G04 Filename: test_1_edit*
G04 Created on : Friday, 01 May 2020 at 17:06*
%FSLAX24Y24*%
%MOIN*%
%ADD10C,0.003937*%
G70*
G90*
G01*
%LPD*%
D10*
X05118Y11417D02*
X05118Y05118D01*
X05118Y05118D02*
X08661Y05118D01*
X08661Y05118D02*
X08661Y11417D01*
X08661Y11417D02*
X05118Y11417D01*
X08661Y11417D02*
X05118Y11417D01*
X08661Y11417D02*
X05118Y11417D01*
M02*

View File

@ -0,0 +1,19 @@
# ----------- START: This is needed only for the examples ----------------
# first set the default location where to search for the files to be open and store it to the ROOT_FOLDER variable
set ROOT_FOLDER [get_sys root_folder_path]
# calculate the resources path for the examples we need to run and store it inside the PATH varaible
set PATH ${ROOT_FOLDER}/assets/examples/files
# ----------- END: This is needed only for the examples ----------------
# set the working path to the path that holds the files we are going to work with
set_path $PATH
# load the GERBER file
open_gerber test.gbr
# load the Excellon ifle
open_excellon test.txt
# plot them all so we can see them on canvas
plot_all

Binary file not shown.

Before

Width:  |  Height:  |  Size: 994 B

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 381 B

View File

@ -3472,8 +3472,7 @@ class CNCjob(Geometry):
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit('[ERROR_NOTCL] %s...' %
_('The loaded Excellon file has no drills'))
self.app.inform.emit('[ERROR_NOTCL] %s...' % _('The loaded Excellon file has no drills'))
return 'fail'
self.z_cut = deepcopy(old_zcut)
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
@ -3499,14 +3498,12 @@ class CNCjob(Geometry):
self.app.inform.emit(_("Finished G-Code generation..."))
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,
feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
multidepth=False, depthpercut=None,
toolchange=False, toolchangez=1.0, toolchangexy="0.0, 0.0", extracut=False, extracut_length=0.2,
startz=None, endz=2.0, endxy='', pp_geometry_name=None, tool_no=1):
def generate_from_multitool_geometry(self, geometry, append=True, tooldia=None, offset=0.0, tolerance=0, z_cut=1.0,
z_move=2.0, feedrate=2.0, feedrate_z=2.0, feedrate_rapid=30,
spindlespeed=None, spindledir='CW', dwell=False, dwelltime=1.0,
multidepth=False, depthpercut=None, toolchange=False, toolchangez=1.0,
toolchangexy="0.0, 0.0", extracut=False, extracut_length=0.2,
startz=None, endz=2.0, endxy='', pp_geometry_name=None, tool_no=1):
"""
Algorithm to generate from multitool Geometry.
@ -5025,8 +5022,7 @@ class CNCjob(Geometry):
return path
def linear2gcode(self, linear, tolerance=0, down=True, up=True,
z_cut=None, z_move=None, zdownrate=None,
def linear2gcode(self, linear, tolerance=0, down=True, up=True, z_cut=None, z_move=None, zdownrate=None,
feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)):
"""
@ -5119,8 +5115,7 @@ class CNCjob(Geometry):
# For Incremental coordinates type G91
# next_x = pt[0] - prev_x
# next_y = pt[1] - prev_y
self.app.inform.emit('[ERROR_NOTCL] %s' %
_('G91 coordinates not implemented ...'))
self.app.inform.emit('[ERROR_NOTCL] %s' % _('G91 coordinates not implemented ...'))
next_x = pt[0]
next_y = pt[1]

View File

@ -28,6 +28,7 @@ class FlatCAMDefaults:
"version": 8.992, # defaults format version, not necessarily equal to app version
"first_run": True,
"units": "MM",
"root_folder_path": '',
"global_serial": 0,
"global_stats": dict(),
"global_tabs_detachable": True,

View File

@ -2837,7 +2837,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.on_grid_snap_triggered(state=True)
self.app.ui.on_grid_snap_triggered(state=True)
self.app.ui.popmenu_disable.setVisible(False)
self.app.ui.cmenu_newmenu.menuAction().setVisible(False)
@ -3226,7 +3226,7 @@ class FlatCAMExcEditor(QtCore.QObject):
spec = {"C": float(tool_dia[0])}
self.new_tools[name] = spec
# add in self.tools the 'solid_geometry' key, the value (a list) is populated bellow
# add in self.tools the 'solid_geometry' key, the value (a list) is populated below
self.new_tools[name]['solid_geometry'] = []
# create the self.drills for the new Excellon object (the one with edited content)
@ -3258,7 +3258,7 @@ class FlatCAMExcEditor(QtCore.QObject):
spec = {"C": float(tool_dia[0])}
self.new_tools[name] = spec
# add in self.tools the 'solid_geometry' key, the value (a list) is populated bellow
# add in self.tools the 'solid_geometry' key, the value (a list) is populated below
self.new_tools[name]['solid_geometry'] = []
# create the self.slots for the new Excellon object (the one with edited content)

View File

@ -4100,7 +4100,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.on_grid_snap_triggered(state=True)
self.app.ui.on_grid_snap_triggered(state=True)
def on_buffer_tool(self):
buff_tool = BufferSelectionTool(self.app, self)

View File

@ -12,6 +12,8 @@ from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Pol
from shapely.ops import cascaded_union
import shapely.affinity as affinity
from vispy.geometry import Rect
import threading
import time
from copy import copy, deepcopy
@ -3701,7 +3703,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
self.app.on_grid_snap_triggered(state=True)
self.app.ui.on_grid_snap_triggered(state=True)
# adjust the visibility of some of the canvas context menu
self.app.ui.popmenu_edit.setVisible(False)
@ -3721,6 +3723,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
except Exception as e:
log.debug("FlatCAMGrbEditor.deactivate_grb_editor() --> %s" % str(e))
self.clear()
# adjust the status of the menu entries related to the editor
self.app.ui.menueditedit.setDisabled(False)
self.app.ui.menueditok.setDisabled(True)
@ -3729,7 +3733,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.app.ui.popmenu_save.setVisible(False)
self.disconnect_canvas_event_handlers()
self.clear()
self.app.ui.grb_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM")
@ -3937,8 +3940,12 @@ class FlatCAMGrbEditor(QtCore.QObject):
pass
def clear(self):
self.thread.quit()
self.active_tool = None
self.selected = []
self.storage_dict.clear()
self.results.clear()
self.shapes.clear(update=True)
self.tool_shape.clear(update=True)
@ -3968,7 +3975,16 @@ class FlatCAMGrbEditor(QtCore.QObject):
file_units = self.gerber_obj.units if self.gerber_obj.units else 'IN'
app_units = self.app.defaults['units']
self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1
# self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1
if file_units == app_units:
self.conversion_factor = 1
else:
if file_units == 'IN':
self.conversion_factor = 25.4
else:
self.conversion_factor = 0.0393700787401575
# Hide original geometry
orig_grb_obj.visible = False
@ -4228,8 +4244,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
else:
new_grb_name = self.edited_obj_name + "_edit"
self.app.worker_task.emit({'fcn': self.new_edited_gerber,
'params': [new_grb_name, self.storage_dict]})
self.app.worker_task.emit({'fcn': self.new_edited_gerber, 'params': [new_grb_name, self.storage_dict]})
@staticmethod
def update_options(obj):
@ -4927,6 +4942,39 @@ class FlatCAMGrbEditor(QtCore.QObject):
# self.app.app_cursor.enabled = False
# self.app.app_cursor.enabled = True
def on_zoom_fit(self):
"""
Callback for zoom-fit request in Gerber Editor
:return: None
"""
log.debug("FlatCAMGrbEditor.on_zoom_fit()")
# calculate all the geometry in the edited Gerber object
edit_geo = []
for ap_code in self.storage_dict:
for geo_el in self.storage_dict[ap_code]['geometry']:
actual_geo = geo_el.geo
if 'solid' in actual_geo:
edit_geo.append(actual_geo['solid'])
all_geo = cascaded_union(edit_geo)
# calculate the bounds values for the edited Gerber object
xmin, ymin, xmax, ymax = all_geo.bounds
if self.app.is_legacy is False:
new_rect = Rect(xmin, ymin, xmax, ymax)
self.app.plotcanvas.fit_view(rect=new_rect)
else:
width = xmax - xmin
height = ymax - ymin
xmin -= 0.05 * width
xmax += 0.05 * width
ymin -= 0.05 * height
ymax += 0.05 * height
self.app.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax)
def get_selected(self):
"""
Returns list of shapes that are selected in the editor.

View File

@ -25,8 +25,8 @@ if '_' not in builtins.__dict__:
class TextEditor(QtWidgets.QWidget):
def __init__(self, app, text=None, plain_text=None):
super().__init__()
def __init__(self, app, text=None, plain_text=None, parent=None):
super().__init__(parent=parent)
self.app = app
self.plain_text = plain_text

View File

@ -2366,7 +2366,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# ########################################################################
# ######################## BUILD PREFERENCES #############################
# ########################################################################
self.general_defaults_form = GeneralPreferencesUI(decimals=self.decimals)
self.gerber_defaults_form = GerberPreferencesUI(decimals=self.decimals)
self.excellon_defaults_form = ExcellonPreferencesUI(decimals=self.decimals)
@ -2381,7 +2380,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
# ########################################################################
# ################## RESTORE THE TOOLBAR STATE from file #################
# ########################################################################
flat_settings = QSettings("Open Source", "FlatCAM")
if flat_settings.contains("saved_gui_state"):
saved_gui_state = flat_settings.value('saved_gui_state')
@ -2439,15 +2437,42 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
del qsettings
self.lock_toolbar(lock=lock_state)
self.on_grid_snap_triggered(state=True)
self.lock_action.triggered[bool].connect(self.lock_toolbar)
self.pref_open_button.clicked.connect(self.on_preferences_open_folder)
self.clear_btn.clicked.connect(self.on_gui_clear)
self.grid_snap_btn.triggered.connect(self.on_grid_snap_triggered)
self.snap_infobar_label.clicked.connect(self.on_grid_icon_snap_clicked)
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# %%%%%%%%%%%%%%%%% GUI Building FINISHED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
def on_grid_snap_triggered(self, state):
"""
:param state: A parameter with the state of the grid, boolean
:return:
"""
if state:
self.snap_infobar_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/snap_filled_16.png'))
else:
self.snap_infobar_label.setPixmap(QtGui.QPixmap(self.app.resource_location + '/snap_16.png'))
self.snap_infobar_label.clicked_state = state
def on_grid_icon_snap_clicked(self):
"""
Slot called by clicking a GUI element, in this case a FCLabel
:return:
"""
if isinstance(self.sender(), FCLabel):
self.grid_snap_btn.trigger()
def eventFilter(self, obj, event):
"""
Filter the ToolTips display based on a Preferences setting
@ -3207,7 +3232,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
else:
self.app.collection.set_active(names_list[active_index-1])
# Select the object in the Tree bellow the current one
# Select the object in the Tree below the current one
if key == QtCore.Qt.Key_Down:
# make sure it works only for the Project Tab who is an instance of KeySensitiveListView
focused_wdg = QtWidgets.QApplication.focusWidget()
@ -3811,10 +3836,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.grb_editor.select_tool('track')
return
# Zoom Fit
# Zoom fit
if key == QtCore.Qt.Key_V or key == 'V':
self.app.grb_editor.launched_from_shortcuts = True
self.app.on_zoom_fit(None)
self.app.grb_editor.on_zoom_fit()
return
# Show Shortcut list

View File

@ -45,7 +45,7 @@ class PreferencesUIManager:
# if Preferences are changed in the Edit -> Preferences tab the value will be set to True
self.preferences_changed_flag = False
# when adding entries here read the comments in the method found bellow named:
# when adding entries here read the comments in the method found below named:
# def new_object(self, kind, name, initialize, active=True, fit=True, plot=True)
self.defaults_form_fields = {
# General App

View File

@ -374,10 +374,25 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
self.splash_cb.stateChanged.connect(self.on_splash_changed)
# Monitor the checkbox from the Application Defaults Tab and show the TCL shell or not depending on it's value
self.shell_startup_cb.clicked.connect(self.app.on_toggle_shell_from_settings)
self.shell_startup_cb.clicked.connect(self.on_toggle_shell_from_settings)
self.language_apply_btn.clicked.connect(lambda: fcTranslate.on_language_apply_click(app=self.app, restart=True))
def on_toggle_shell_from_settings(self, state):
"""
Toggle shell: if is visible close it, if it is closed then open it
:return: None
"""
self.app.defaults.report_usage("on_toggle_shell_from_settings()")
if state is True:
if not self.app.ui.shell_dock.isVisible():
self.app.ui.shell_dock.show()
else:
if self.app.ui.shell_dock.isVisible():
self.app.ui.shell_dock.hide()
@staticmethod
def on_splash_changed(state):
qsettings = QSettings("Open Source", "FlatCAM")

View File

@ -377,7 +377,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI2):
self.app.connect_toolbar_signals()
self.app.ui.grid_snap_btn.setChecked(True)
self.app.on_grid_snap_triggered(state=True)
self.app.ui.on_grid_snap_triggered(state=True)
self.app.ui.grid_gap_x_entry.setText(str(self.app.defaults["global_gridx"]))
self.app.ui.grid_gap_y_entry.setText(str(self.app.defaults["global_gridy"]))

View File

@ -364,7 +364,7 @@ class CNCJobObject(FlatCAMObj, CNCjob):
self.units_found = self.app.defaults['units']
# this signal has to be connected to it's slot before the defaults are populated
# the decision done in the slot has to override the default value set bellow
# the decision done in the slot has to override the default value set below
self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked)
self.form_fields.update({

View File

@ -1114,6 +1114,26 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.tipanglelabel.show()
self.ui.tipangle_entry.show()
self.ui.cutz_entry.setDisabled(True)
self.ui.cutzlabel.setToolTip(
_("Disabled because the tool is V-shape.\n"
"For V-shape tools the depth of cut is\n"
"calculated from other parameters like:\n"
"- 'V-tip Angle' -> angle at the tip of the tool\n"
"- 'V-tip Dia' -> diameter at the tip of the tool \n"
"- Tool Dia -> 'Dia' column found in the Tool Table\n"
"NB: a value of zero means that Tool Dia = 'V-tip Dia'"
)
)
self.ui.cutz_entry.setToolTip(
_("Disabled because the tool is V-shape.\n"
"For V-shape tools the depth of cut is\n"
"calculated from other parameters like:\n"
"- 'V-tip Angle' -> angle at the tip of the tool\n"
"- 'V-tip Dia' -> diameter at the tip of the tool \n"
"- Tool Dia -> 'Dia' column found in the Tool Table\n"
"NB: a value of zero means that Tool Dia = 'V-tip Dia'"
)
)
self.update_cutz()
else:
@ -1122,6 +1142,12 @@ class GeometryObject(FlatCAMObj, Geometry):
self.ui.tipanglelabel.hide()
self.ui.tipangle_entry.hide()
self.ui.cutz_entry.setDisabled(False)
self.ui.cutzlabel.setToolTip(
_("Cutting depth (negative)\n"
"below the copper surface."
)
)
self.ui.cutz_entry.setToolTip('')
def update_cutz(self):
vdia = float(self.ui.tipdia_entry.get_value())

View File

@ -50,15 +50,14 @@ class ScriptObject(FlatCAMObj):
self.units = ''
self.script_editor_tab = None
self.ser_attrs = ['options', 'kind', 'source_file']
self.source_file = ''
self.script_code = ''
self.units_found = self.app.defaults['units']
# self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
self.script_editor_tab = TextEditor(app=self.app, plain_text=True)
def set_ui(self, ui):
"""
Sets the Object UI in Selected Tab for the FlatCAM Script type of object.
@ -87,6 +86,8 @@ class ScriptObject(FlatCAMObj):
'<span style="color:red;"><b>Advanced</b></span>'
))
self.script_editor_tab = TextEditor(app=self.app, plain_text=True, parent=self.app.ui)
# 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()):
@ -99,8 +100,8 @@ class ScriptObject(FlatCAMObj):
# self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
# self.script_editor_tab.setObjectName(self.options['name'])
self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
self.script_editor_tab.setObjectName(self.options['name'])
# self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor"))
# self.script_editor_tab.setObjectName(self.options['name'])
# first clear previous text in text editor (if any)
# self.script_editor_tab.code_editor.clear()
@ -111,7 +112,7 @@ class ScriptObject(FlatCAMObj):
self.script_editor_tab.buttonRun.show()
# Switch plot_area to CNCJob tab
# Switch plot_area to Script Editor tab
self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)"
@ -150,6 +151,32 @@ class ScriptObject(FlatCAMObj):
def build_ui(self):
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'])
self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab)
def parse_file(self, filename):
"""
Will set an attribute of the object, self.source_file, with the parsed data.
:param filename: Tcl Script file to parse
:return: None
"""
with open(filename, "r") as opened_script:
script_content = opened_script.readlines()
script_content = ''.join(script_content)
self.source_file = script_content
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

View File

@ -426,7 +426,7 @@ class Excellon(Geometry):
# it's possible that tool definition has only tool number and no diameter info
# (those could be in another file like PCB Wizard do)
# then match.group(2) = None and float(None) will create the exception
# the bellow construction is so each tool will have a slightly different diameter
# the below construction is so each tool will have a slightly different diameter
# starting with a default value, to allow Excellon editing after that
self.diameterless = True
self.app.inform.emit('[WARNING] %s%s %s' %

View File

@ -293,12 +293,12 @@ class CutOut(FlatCAMTool):
font-weight: bold;
}
""")
self.layout.addWidget(self.rect_cutout_object_btn)
grid0.addWidget(self.rect_cutout_object_btn, 21, 0, 1, 2)
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
grid0.addWidget(separator_line, 21, 0, 1, 2)
grid0.addWidget(separator_line, 22, 0, 1, 2)
# Title5
title_manual_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % _('B. Manual Bridge Gaps'))
@ -307,7 +307,7 @@ class CutOut(FlatCAMTool):
"This is done by mouse clicking on the perimeter of the\n"
"Geometry object that is used as a cutout object. ")
)
grid0.addWidget(title_manual_label, 22, 0, 1, 2)
grid0.addWidget(title_manual_label, 23, 0, 1, 2)
# Manual Geo Object
self.man_object_combo = FCComboBox()
@ -322,8 +322,8 @@ class CutOut(FlatCAMTool):
)
# self.man_object_label.setMinimumWidth(60)
grid0.addWidget(self.man_object_label, 23, 0, 1, 2)
grid0.addWidget(self.man_object_combo, 24, 0, 1, 2)
grid0.addWidget(self.man_object_label, 25, 0, 1, 2)
grid0.addWidget(self.man_object_combo, 26, 0, 1, 2)
self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry"))
self.man_geo_creation_btn.setToolTip(
@ -338,7 +338,7 @@ class CutOut(FlatCAMTool):
font-weight: bold;
}
""")
grid0.addWidget(self.man_geo_creation_btn, 25, 0, 1, 2)
grid0.addWidget(self.man_geo_creation_btn, 28, 0, 1, 2)
self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps"))
self.man_gaps_creation_btn.setToolTip(
@ -354,7 +354,7 @@ class CutOut(FlatCAMTool):
font-weight: bold;
}
""")
grid0.addWidget(self.man_gaps_creation_btn, 27, 0, 1, 2)
grid0.addWidget(self.man_gaps_creation_btn, 30, 0, 1, 2)
self.layout.addStretch()
@ -394,6 +394,9 @@ class CutOut(FlatCAMTool):
self.x_pos = None
self.y_pos = None
# store the default data for the resulting Geometry Object
self.default_data = {}
# Signals
self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
@ -454,6 +457,48 @@ class CutOut(FlatCAMTool):
self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
self.type_obj_radio.set_value('grb')
self.default_data.update({
"plot": True,
"cutz": float(self.app.defaults["geometry_cutz"]),
"multidepth": self.app.defaults["geometry_multidepth"],
"depthperpass": float(self.app.defaults["geometry_depthperpass"]),
"vtipdia": float(self.app.defaults["geometry_vtipdia"]),
"vtipangle": float(self.app.defaults["geometry_vtipangle"]),
"travelz": float(self.app.defaults["geometry_travelz"]),
"feedrate": float(self.app.defaults["geometry_feedrate"]),
"feedrate_z": float(self.app.defaults["geometry_feedrate_z"]),
"feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]),
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
"dwell": self.app.defaults["geometry_dwell"],
"dwelltime": float(self.app.defaults["geometry_dwelltime"]),
"ppname_g": self.app.defaults["geometry_ppname_g"],
"extracut": self.app.defaults["geometry_extracut"],
"extracut_length": float(self.app.defaults["geometry_extracut_length"]),
"toolchange": self.app.defaults["geometry_toolchange"],
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
"toolchangez": float(self.app.defaults["geometry_toolchangez"]),
"startz": self.app.defaults["geometry_startz"],
"endz": float(self.app.defaults["geometry_endz"]),
# NCC
"tools_nccoperation": self.app.defaults["tools_nccoperation"],
"tools_nccmilling_type": self.app.defaults["tools_nccmilling_type"],
"tools_nccoverlap": float(self.app.defaults["tools_nccoverlap"]),
"tools_nccmargin": float(self.app.defaults["tools_nccmargin"]),
"tools_nccmethod": self.app.defaults["tools_nccmethod"],
"tools_nccconnect": self.app.defaults["tools_nccconnect"],
"tools_ncccontour": self.app.defaults["tools_ncccontour"],
"tools_ncc_offset_choice": self.app.defaults["tools_ncc_offset_choice"],
"tools_ncc_offset_value": float(self.app.defaults["tools_ncc_offset_value"]),
# Paint
"tools_paintoverlap": float(self.app.defaults["tools_paintoverlap"]),
"tools_paintmargin": float(self.app.defaults["tools_paintmargin"]),
"tools_paintmethod": self.app.defaults["tools_paintmethod"],
"tools_pathconnect": self.app.defaults["tools_pathconnect"],
"tools_paintcontour": self.app.defaults["tools_paintcontour"],
})
def on_freeform_cutout(self):
log.debug("Cutout.on_freeform_cutout() was launched ...")
@ -622,8 +667,8 @@ class CutOut(FlatCAMTool):
solid_geo += cutout_handler(geom=geom_struct)
geo_obj.solid_geometry = deepcopy(solid_geo)
xmin, ymin, xmax, ymax = recursive_bounds(geo_obj.solid_geometry)
geo_obj.solid_geometry = deepcopy(solid_geo)
geo_obj.options['xmin'] = xmin
geo_obj.options['ymin'] = ymin
geo_obj.options['xmax'] = xmax
@ -633,6 +678,23 @@ class CutOut(FlatCAMTool):
geo_obj.options['multidepth'] = self.mpass_cb.get_value()
geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
geo_obj.tools.update({
1: {
'tooldia': str(dia),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': 'C1',
'data': self.default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
geo_obj.multigeo = True
geo_obj.tools[1]['data']['name'] = outname
geo_obj.tools[1]['data']['cutz'] = self.cutz_entry.get_value()
geo_obj.tools[1]['data']['multidepth'] = self.mpass_cb.get_value()
geo_obj.tools[1]['data']['depthperpass'] = self.maxdepth_entry.get_value()
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
@ -759,6 +821,7 @@ class CutOut(FlatCAMTool):
return proc_geometry
if kind == 'single':
# fuse the lines
object_geo = unary_union(object_geo)
xmin, ymin, xmax, ymax = object_geo.bounds
@ -805,11 +868,28 @@ class CutOut(FlatCAMTool):
_("Rectangular cutout with negative margin is not possible."))
return "fail"
geo_obj.solid_geometry = deepcopy(solid_geo)
geo_obj.options['cnctooldia'] = str(dia)
geo_obj.options['cutz'] = self.cutz_entry.get_value()
geo_obj.options['multidepth'] = self.mpass_cb.get_value()
geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
geo_obj.solid_geometry = deepcopy(solid_geo)
geo_obj.tools.update({
1: {
'tooldia': str(dia),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': 'C1',
'data': self.default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
geo_obj.multigeo = True
geo_obj.tools[1]['data']['name'] = outname
geo_obj.tools[1]['data']['cutz'] = self.cutz_entry.get_value()
geo_obj.tools[1]['data']['multidepth'] = self.mpass_cb.get_value()
geo_obj.tools[1]['data']['depthperpass'] = self.maxdepth_entry.get_value()
outname = cutout_obj.options["name"] + "_cutout"
ret = self.app.new_object('geometry', outname, geo_init)
@ -954,6 +1034,23 @@ class CutOut(FlatCAMTool):
geo_obj.options['multidepth'] = self.mpass_cb.get_value()
geo_obj.options['depthperpass'] = self.maxdepth_entry.get_value()
geo_obj.tools.update({
1: {
'tooldia': str(dia),
'offset': 'Path',
'offset_value': 0.0,
'type': _('Rough'),
'tool_type': 'C1',
'data': self.default_data,
'solid_geometry': geo_obj.solid_geometry
}
})
geo_obj.multigeo = True
geo_obj.tools[1]['data']['name'] = outname
geo_obj.tools[1]['data']['cutz'] = self.cutz_entry.get_value()
geo_obj.tools[1]['data']['multidepth'] = self.mpass_cb.get_value()
geo_obj.tools[1]['data']['depthperpass'] = self.maxdepth_entry.get_value()
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)

View File

@ -128,7 +128,7 @@ class Film(FlatCAMTool):
self.tf_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
self.tf_box_combo_label.setToolTip(
_("The actual object that is used a container for the\n "
_("The actual object that is used as container for the\n "
"selected object for which we create the film.\n"
"Usually it is the PCB outline but it can be also the\n"
"same object for which the film is created.")

View File

@ -14,6 +14,8 @@ from copy import deepcopy
import numpy as np
import shapely.affinity as affinity
from shapely.ops import unary_union
from shapely.geometry import LineString
import gettext
import FlatCAMTranslation as fcTranslate
@ -136,7 +138,7 @@ class Panelize(FlatCAMTool):
self.box_combo.is_last = True
self.box_combo.setToolTip(
_("The actual object that is used a container for the\n "
_("The actual object that is used as container for the\n "
"selected object that is to be panelized.")
)
form_layout.addRow(self.box_combo)
@ -402,19 +404,18 @@ class Panelize(FlatCAMTool):
def on_panelize(self):
name = self.object_combo.currentText()
# Get source object.
# Get source object to be panelized.
try:
panel_obj = self.app.collection.get_by_name(str(name))
panel_source_obj = self.app.collection.get_by_name(str(name))
except Exception as e:
log.debug("Panelize.on_panelize() --> %s" % str(e))
self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
(_("Could not retrieve object"), name))
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), name))
return "Could not retrieve object: %s" % name
if panel_obj is None:
if panel_source_obj is None:
self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
(_("Object not found"), panel_obj))
return "Object not found: %s" % panel_obj
(_("Object not found"), panel_source_obj))
return "Object not found: %s" % panel_source_obj
boxname = self.box_combo.currentText()
@ -422,17 +423,15 @@ class Panelize(FlatCAMTool):
box = self.app.collection.get_by_name(boxname)
except Exception as e:
log.debug("Panelize.on_panelize() --> %s" % str(e))
self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
(_("Could not retrieve object"), boxname))
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), boxname))
return "Could not retrieve object: %s" % boxname
if box is None:
self.app.inform.emit('[WARNING_NOTCL]%s: %s' %
(_("No object Box. Using instead"), panel_obj))
self.app.inform.emit('[WARNING_NOTCL]%s: %s' % (_("No object Box. Using instead"), panel_source_obj))
self.reference_radio.set_value('bbox')
if self.reference_radio.get_value() == 'bbox':
box = panel_obj
box = panel_source_obj
self.outname = name + '_panelized'
@ -478,20 +477,20 @@ class Panelize(FlatCAMTool):
rows -= 1
panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
if panel_obj.kind == 'excellon' or panel_obj.kind == 'geometry':
if panel_source_obj.kind == 'excellon' or panel_source_obj.kind == 'geometry':
# make a copy of the panelized Excellon or Geometry tools
copied_tools = {}
for tt, tt_val in list(panel_obj.tools.items()):
for tt, tt_val in list(panel_source_obj.tools.items()):
copied_tools[tt] = deepcopy(tt_val)
if panel_obj.kind == 'gerber':
if panel_source_obj.kind == 'gerber':
# make a copy of the panelized Gerber apertures
copied_apertures = {}
for tt, tt_val in list(panel_obj.apertures.items()):
for tt, tt_val in list(panel_source_obj.apertures.items()):
copied_apertures[tt] = deepcopy(tt_val)
def panelize_2():
if panel_obj is not None:
def panelize_worker():
if panel_source_obj is not None:
self.app.inform.emit(_("Generating panel ... "))
def job_init_excellon(obj_fin, app_obj):
@ -501,15 +500,15 @@ class Panelize(FlatCAMTool):
obj_fin.slots = []
obj_fin.solid_geometry = []
for option in panel_obj.options:
for option in panel_source_obj.options:
if option != 'name':
try:
obj_fin.options[option] = panel_obj.options[option]
obj_fin.options[option] = panel_source_obj.options[option]
except KeyError:
log.warning("Failed to copy option. %s" % str(option))
geo_len_drills = len(panel_obj.drills) if panel_obj.drills else 0
geo_len_slots = len(panel_obj.slots) if panel_obj.slots else 0
geo_len_drills = len(panel_source_obj.drills) if panel_source_obj.drills else 0
geo_len_slots = len(panel_source_obj.slots) if panel_source_obj.slots else 0
element = 0
for row in range(rows):
@ -518,9 +517,9 @@ class Panelize(FlatCAMTool):
element += 1
old_disp_number = 0
if panel_obj.drills:
if panel_source_obj.drills:
drill_nr = 0
for tool_dict in panel_obj.drills:
for tool_dict in panel_source_obj.drills:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
@ -543,9 +542,9 @@ class Panelize(FlatCAMTool):
disp_number))
old_disp_number = disp_number
if panel_obj.slots:
if panel_source_obj.slots:
slot_nr = 0
for tool_dict in panel_obj.slots:
for tool_dict in panel_source_obj.slots:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
@ -574,8 +573,8 @@ class Panelize(FlatCAMTool):
currenty += lenghty
obj_fin.create_geometry()
obj_fin.zeros = panel_obj.zeros
obj_fin.units = panel_obj.units
obj_fin.zeros = panel_source_obj.zeros
obj_fin.units = panel_source_obj.units
self.app.proc_container.update_view_text('')
def job_init_geometry(obj_fin, app_obj):
@ -598,36 +597,36 @@ class Panelize(FlatCAMTool):
obj_fin.solid_geometry = []
# create the initial structure on which to create the panel
if panel_obj.kind == 'geometry':
obj_fin.multigeo = panel_obj.multigeo
if panel_source_obj.kind == 'geometry':
obj_fin.multigeo = panel_source_obj.multigeo
obj_fin.tools = copied_tools
if panel_obj.multigeo is True:
for tool in panel_obj.tools:
if panel_source_obj.multigeo is True:
for tool in panel_source_obj.tools:
obj_fin.tools[tool]['solid_geometry'][:] = []
elif panel_obj.kind == 'gerber':
elif panel_source_obj.kind == 'gerber':
obj_fin.apertures = copied_apertures
for ap in obj_fin.apertures:
obj_fin.apertures[ap]['geometry'] = []
# find the number of polygons in the source solid_geometry
geo_len = 0
if panel_obj.kind == 'geometry':
if panel_obj.multigeo is True:
for tool in panel_obj.tools:
if panel_source_obj.kind == 'geometry':
if panel_source_obj.multigeo is True:
for tool in panel_source_obj.tools:
try:
geo_len += len(panel_obj.tools[tool]['solid_geometry'])
geo_len += len(panel_source_obj.tools[tool]['solid_geometry'])
except TypeError:
geo_len += 1
else:
try:
geo_len = len(panel_obj.solid_geometry)
geo_len = len(panel_source_obj.solid_geometry)
except TypeError:
geo_len = 1
elif panel_obj.kind == 'gerber':
for ap in panel_obj.apertures:
if 'geometry' in panel_obj.apertures[ap]:
elif panel_source_obj.kind == 'gerber':
for ap in panel_source_obj.apertures:
if 'geometry' in panel_source_obj.apertures[ap]:
try:
geo_len += len(panel_obj.apertures[ap]['geometry'])
geo_len += len(panel_source_obj.apertures[ap]['geometry'])
except TypeError:
geo_len += 1
@ -639,29 +638,23 @@ class Panelize(FlatCAMTool):
element += 1
old_disp_number = 0
if panel_obj.kind == 'geometry':
if panel_obj.multigeo is True:
for tool in panel_obj.tools:
# Will panelize a Geometry Object
if panel_source_obj.kind == 'geometry':
if panel_source_obj.multigeo is True:
for tool in panel_source_obj.tools:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
# geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
# if isinstance(geo, list):
# obj_fin.tools[tool]['solid_geometry'] += geo
# else:
# obj_fin.tools[tool]['solid_geometry'].append(geo)
# calculate the number of polygons
geo_len = len(panel_obj.tools[tool]['solid_geometry'])
geo_len = len(panel_source_obj.tools[tool]['solid_geometry'])
pol_nr = 0
for geo_el in panel_obj.tools[tool]['solid_geometry']:
for geo_el in panel_source_obj.tools[tool]['solid_geometry']:
trans_geo = translate_recursion(geo_el)
obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
pol_nr += 1
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
if old_disp_number < disp_number <= 100:
self.app.proc_container.update_view_text(' %s: %d %d%%' %
(_("Copy"),
@ -669,23 +662,18 @@ class Panelize(FlatCAMTool):
disp_number))
old_disp_number = disp_number
else:
# geo = translate_recursion(panel_obj.solid_geometry)
# if isinstance(geo, list):
# obj_fin.solid_geometry += geo
# else:
# obj_fin.solid_geometry.append(geo)
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
try:
# calculate the number of polygons
geo_len = len(panel_obj.solid_geometry)
geo_len = len(panel_source_obj.solid_geometry)
except TypeError:
geo_len = 1
pol_nr = 0
try:
for geo_el in panel_obj.solid_geometry:
for geo_el in panel_source_obj.solid_geometry:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
@ -702,21 +690,18 @@ class Panelize(FlatCAMTool):
int(element),
disp_number))
old_disp_number = disp_number
except TypeError:
trans_geo = translate_recursion(panel_obj.solid_geometry)
trans_geo = translate_recursion(panel_source_obj.solid_geometry)
obj_fin.solid_geometry.append(trans_geo)
# Will panelize a Gerber Object
else:
# geo = translate_recursion(panel_obj.solid_geometry)
# if isinstance(geo, list):
# obj_fin.solid_geometry += geo
# else:
# obj_fin.solid_geometry.append(geo)
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
try:
for geo_el in panel_obj.solid_geometry:
for geo_el in panel_source_obj.solid_geometry:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
@ -724,21 +709,21 @@ class Panelize(FlatCAMTool):
trans_geo = translate_recursion(geo_el)
obj_fin.solid_geometry.append(trans_geo)
except TypeError:
trans_geo = translate_recursion(panel_obj.solid_geometry)
trans_geo = translate_recursion(panel_source_obj.solid_geometry)
obj_fin.solid_geometry.append(trans_geo)
for apid in panel_obj.apertures:
for apid in panel_source_obj.apertures:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
if 'geometry' in panel_obj.apertures[apid]:
if 'geometry' in panel_source_obj.apertures[apid]:
try:
# calculate the number of polygons
geo_len = len(panel_obj.apertures[apid]['geometry'])
geo_len = len(panel_source_obj.apertures[apid]['geometry'])
except TypeError:
geo_len = 1
pol_nr = 0
for el in panel_obj.apertures[apid]['geometry']:
for el in panel_source_obj.apertures[apid]['geometry']:
if self.app.abort_flag:
# graceful abort requested by the user
raise grace
@ -771,20 +756,34 @@ class Panelize(FlatCAMTool):
currentx += lenghtx
currenty += lenghty
print("before", obj_fin.tools)
if panel_source_obj.kind == 'geometry' and panel_source_obj.multigeo is True:
# I'm going to do this only here as a fix for panelizing cutouts
# I'm going to separate linestrings out of the solid geometry from other
# possible type of elements and apply unary_union on them to fuse them
for tool in obj_fin.tools:
lines = []
other_geo = []
for geo in obj_fin.tools[tool]['solid_geometry']:
if isinstance(geo, LineString):
lines.append(geo)
else:
other_geo.append(geo)
fused_lines = list(unary_union(lines))
obj_fin.tools[tool]['solid_geometry'] = fused_lines + other_geo
print("after", obj_fin.tools)
if panel_type == 'gerber':
self.app.inform.emit('%s' % _("Generating panel ... Adding the Gerber code."))
obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
local_use=obj_fin, use_thread=False)
# app_obj.log.debug("Found %s geometries. Creating a panel geometry cascaded union ..." %
# len(obj_fin.solid_geometry))
# obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
# app_obj.log.debug("Finished creating a cascaded union for the panel.")
self.app.proc_container.update_view_text('')
self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns))))
if panel_obj.kind == 'excellon':
if panel_source_obj.kind == 'excellon':
self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
else:
self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True)
@ -801,7 +800,7 @@ class Panelize(FlatCAMTool):
def job_thread(app_obj):
try:
panelize_2()
panelize_worker()
self.app.inform.emit('[success] %s' % _("Panel created successfully."))
except Exception as ee:
proc.done()

View File

@ -421,8 +421,7 @@ class PcbWizard(FlatCAMTool):
ret = excellon_obj.parse_file(file_obj=excellon_fileobj)
if ret == "fail":
app_obj.log.debug("Excellon parsing failed.")
app_obj.inform.emit('[ERROR_NOTCL] %s' %
_("This is not Excellon file."))
app_obj.inform.emit('[ERROR_NOTCL] %s' % _("This is not Excellon file."))
return "fail"
except IOError:
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot parse file"), self.outname))
@ -443,8 +442,7 @@ class PcbWizard(FlatCAMTool):
for tool in excellon_obj.tools:
if excellon_obj.tools[tool]['solid_geometry']:
return
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' %
(_("No geometry found in file"), name))
app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("No geometry found in file"), name))
return "fail"
if excellon_fileobj is not None and excellon_fileobj != '':
@ -463,12 +461,9 @@ class PcbWizard(FlatCAMTool):
self.app.file_opened.emit("excellon", name)
# GUI feedback
self.app.inform.emit('[success] %s: %s' %
(_("Imported"), name))
self.app.inform.emit('[success] %s: %s' % (_("Imported"), name))
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
else:
self.app.inform.emit('[WARNING_NOTCL] %s' %
_('Excellon merging is in progress. Please wait...'))
self.app.inform.emit('[WARNING_NOTCL] %s' % _('Excellon merging is in progress. Please wait...'))
else:
self.app.inform.emit('[ERROR_NOTCL] %s' %
_('The imported Excellon file is None.'))
self.app.inform.emit('[ERROR_NOTCL] %s' % _('The imported Excellon file is empty.'))

View File

@ -155,7 +155,7 @@ class SolderPaste(FlatCAMTool):
step1_lbl = QtWidgets.QLabel("<b>%s:</b>" % _('STEP 1'))
step1_lbl.setToolTip(
_("First step is to select a number of nozzle tools for usage\n"
"and then optionally modify the GCode parameters bellow.")
"and then optionally modify the GCode parameters below.")
)
step1_description_lbl = QtWidgets.QLabel(_("Select tools.\n"
"Modify parameters."))

View File

@ -97,7 +97,7 @@ class ToolSub(FlatCAMTool):
form_layout.addRow(self.sub_gerber_label, self.sub_gerber_combo)
self.intersect_btn = FCButton(_('Substract Gerber'))
self.intersect_btn = FCButton(_('Subtract Gerber'))
self.intersect_btn.setToolTip(
_("Will remove the area occupied by the subtractor\n"
"Gerber from the Target Gerber.\n"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -6789,7 +6789,7 @@ msgstr ""
#: flatcamGUI/ObjectUI.py:436
msgid ""
"When the isolation geometry is generated,\n"
"by checking this, the area of the object bellow\n"
"by checking this, the area of the object below\n"
"will be subtracted from the isolation geometry."
msgstr ""
@ -13014,7 +13014,7 @@ msgstr ""
#: flatcamTools/ToolFilm.py:131
msgid ""
"The actual object that is used a container for the\n"
"The actual object that is used as container for the\n"
" selected object for which we create the film.\n"
"Usually it is the PCB outline but it can be also the\n"
"same object for which the film is created."
@ -14116,7 +14116,7 @@ msgid "Excellon merging is in progress. Please wait..."
msgstr ""
#: flatcamTools/ToolPcbWizard.py:474
msgid "The imported Excellon file is None."
msgid "The imported Excellon file is empty."
msgstr ""
#: flatcamTools/ToolProperties.py:131
@ -14604,7 +14604,7 @@ msgstr ""
#: flatcamTools/ToolSolderPaste.py:158
msgid ""
"First step is to select a number of nozzle tools for usage\n"
"and then optionally modify the GCode parameters bellow."
"and then optionally modify the GCode parameters below."
msgstr ""
#: flatcamTools/ToolSolderPaste.py:161
@ -14800,7 +14800,7 @@ msgid ""
msgstr ""
#: flatcamTools/ToolSub.py:100
msgid "Substract Gerber"
msgid "Subtract Gerber"
msgstr ""
#: flatcamTools/ToolSub.py:102

View File

@ -34,7 +34,7 @@ class TclCommandJoinExcellon(TclCommand):
help = {
'main': "Runs a merge operation (join) on the Excellon objects.\n"
"The names of the Excellon objects to be merged will be entered after the outname,\n"
"separated by spaces. See the example bellow.\n"
"separated by spaces. See the example below.\n"
"WARNING: if the name of an Excellon objects has spaces, enclose the name with quotes.",
'args': collections.OrderedDict([
('outname', 'Name of the new Excellon Object made by joining of other Excellon objects. Required'),

View File

@ -34,7 +34,7 @@ class TclCommandJoinGeometry(TclCommand):
help = {
'main': "Runs a merge operation (join) on the Geometry objects.\n"
"The names of the Geometry objects to be merged will be entered after the outname,\n"
"separated by spaces. See the example bellow.\n"
"separated by spaces. See the example below.\n"
"WARNING: if the name of an Geometry objects has spaces, enclose the name with quotes.",
'args': collections.OrderedDict([
('outname', 'Name of the new Geometry Object made by joining of other Geometry objects. Required'),