commit
c7a2b69637
27
FlatCAM.py
27
FlatCAM.py
|
@ -1,7 +1,8 @@
|
|||
import sys
|
||||
import sys, os
|
||||
from PyQt5 import sip
|
||||
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
from FlatCAMApp import App
|
||||
from multiprocessing import freeze_support
|
||||
import VisPyPatches
|
||||
|
@ -31,7 +32,31 @@ if __name__ == '__main__':
|
|||
debug_trace()
|
||||
VisPyPatches.apply_patches()
|
||||
|
||||
# apply High DPI support
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("hdpi"):
|
||||
hdpi_support = settings.value('hdpi', type=int)
|
||||
else:
|
||||
hdpi_support = 0
|
||||
|
||||
if hdpi_support == 2:
|
||||
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
|
||||
else:
|
||||
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0"
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
# apply style
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("style"):
|
||||
style = settings.value('style', type=str)
|
||||
app.setStyle(style)
|
||||
|
||||
if hdpi_support == 2:
|
||||
app.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
||||
else:
|
||||
app.setAttribute(Qt.AA_EnableHighDpiScaling, False)
|
||||
|
||||
fc = App()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
|
468
FlatCAMApp.py
468
FlatCAMApp.py
|
@ -14,6 +14,7 @@ import os
|
|||
import random
|
||||
import logging
|
||||
import simplejson as json
|
||||
import lzma
|
||||
|
||||
import re
|
||||
import os
|
||||
|
@ -92,8 +93,8 @@ class App(QtCore.QObject):
|
|||
log.addHandler(handler)
|
||||
|
||||
# Version
|
||||
version = 8.909
|
||||
version_date = "2019/02/16"
|
||||
version = 8.910
|
||||
version_date = "2019/02/23"
|
||||
beta = True
|
||||
|
||||
# current date now
|
||||
|
@ -311,6 +312,9 @@ class App(QtCore.QObject):
|
|||
"global_send_stats": self.general_defaults_form.general_app_group.send_stats_cb,
|
||||
"global_project_at_startup": self.general_defaults_form.general_app_group.project_startup_cb,
|
||||
"global_project_autohide": self.general_defaults_form.general_app_group.project_autohide_cb,
|
||||
"global_app_level": self.general_defaults_form.general_app_group.app_level_radio,
|
||||
"global_compression_level": self.general_defaults_form.general_app_group.compress_combo,
|
||||
"global_save_compressed": self.general_defaults_form.general_app_group.save_type_cb,
|
||||
|
||||
"global_gridx": self.general_defaults_form.general_gui_group.gridx_entry,
|
||||
"global_gridy": self.general_defaults_form.general_gui_group.gridy_entry,
|
||||
|
@ -457,6 +461,7 @@ class App(QtCore.QObject):
|
|||
"tools_panelize_constrain": self.tools_defaults_form.tools_panelize_group.pconstrain_cb,
|
||||
"tools_panelize_constrainx": self.tools_defaults_form.tools_panelize_group.px_width_entry,
|
||||
"tools_panelize_constrainy": self.tools_defaults_form.tools_panelize_group.py_height_entry,
|
||||
"tools_panelize_panel_type": self.tools_defaults_form.tools_panelize_group.panel_type_radio,
|
||||
|
||||
"tools_calc_vshape_tip_dia": self.tools_defaults_form.tools_calculators_group.tip_dia_entry,
|
||||
"tools_calc_vshape_tip_angle": self.tools_defaults_form.tools_calculators_group.tip_angle_entry,
|
||||
|
@ -464,16 +469,56 @@ class App(QtCore.QObject):
|
|||
"tools_calc_electro_length": self.tools_defaults_form.tools_calculators_group.pcblength_entry,
|
||||
"tools_calc_electro_width": self.tools_defaults_form.tools_calculators_group.pcbwidth_entry,
|
||||
"tools_calc_electro_cdensity": self.tools_defaults_form.tools_calculators_group.cdensity_entry,
|
||||
"tools_calc_electro_growth": self.tools_defaults_form.tools_calculators_group.growth_entry
|
||||
"tools_calc_electro_growth": self.tools_defaults_form.tools_calculators_group.growth_entry,
|
||||
|
||||
"tools_transform_rotate": self.tools_defaults_form.tools_transform_group.rotate_entry,
|
||||
"tools_transform_skew_x": self.tools_defaults_form.tools_transform_group.skewx_entry,
|
||||
"tools_transform_skew_y": self.tools_defaults_form.tools_transform_group.skewy_entry,
|
||||
"tools_transform_scale_x": self.tools_defaults_form.tools_transform_group.scalex_entry,
|
||||
"tools_transform_scale_y": self.tools_defaults_form.tools_transform_group.scaley_entry,
|
||||
"tools_transform_scale_link": self.tools_defaults_form.tools_transform_group.link_cb,
|
||||
"tools_transform_scale_reference": self.tools_defaults_form.tools_transform_group.reference_cb,
|
||||
"tools_transform_offset_x": self.tools_defaults_form.tools_transform_group.offx_entry,
|
||||
"tools_transform_offset_y": self.tools_defaults_form.tools_transform_group.offy_entry,
|
||||
"tools_transform_mirror_reference": self.tools_defaults_form.tools_transform_group.mirror_reference_cb,
|
||||
"tools_transform_mirror_point": self.tools_defaults_form.tools_transform_group.flip_ref_entry,
|
||||
|
||||
"tools_solderpaste_tools": self.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry,
|
||||
"tools_solderpaste_new": self.tools_defaults_form.tools_solderpaste_group.addtool_entry,
|
||||
"tools_solderpaste_z_start": self.tools_defaults_form.tools_solderpaste_group.z_start_entry,
|
||||
"tools_solderpaste_z_dispense": self.tools_defaults_form.tools_solderpaste_group.z_dispense_entry,
|
||||
"tools_solderpaste_z_stop": self.tools_defaults_form.tools_solderpaste_group.z_stop_entry,
|
||||
"tools_solderpaste_z_travel": self.tools_defaults_form.tools_solderpaste_group.z_travel_entry,
|
||||
"tools_solderpaste_z_toolchange": self.tools_defaults_form.tools_solderpaste_group.z_toolchange_entry,
|
||||
"tools_solderpaste_xy_toolchange": self.tools_defaults_form.tools_solderpaste_group.xy_toolchange_entry,
|
||||
"tools_solderpaste_frxy": self.tools_defaults_form.tools_solderpaste_group.frxy_entry,
|
||||
"tools_solderpaste_frz": self.tools_defaults_form.tools_solderpaste_group.frz_entry,
|
||||
"tools_solderpaste_frz_dispense": self.tools_defaults_form.tools_solderpaste_group.frz_dispense_entry,
|
||||
"tools_solderpaste_speedfwd": self.tools_defaults_form.tools_solderpaste_group.speedfwd_entry,
|
||||
"tools_solderpaste_dwellfwd": self.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry,
|
||||
"tools_solderpaste_speedrev": self.tools_defaults_form.tools_solderpaste_group.speedrev_entry,
|
||||
"tools_solderpaste_dwellrev": self.tools_defaults_form.tools_solderpaste_group.dwellrev_entry,
|
||||
"tools_solderpaste_pp": self.tools_defaults_form.tools_solderpaste_group.pp_combo
|
||||
|
||||
}
|
||||
# loads postprocessors
|
||||
|
||||
#############################
|
||||
#### LOAD POSTPROCESSORS ####
|
||||
#############################
|
||||
|
||||
self.postprocessors = load_postprocessors(self)
|
||||
|
||||
for name in list(self.postprocessors.keys()):
|
||||
# 'Paste' postprocessors are to be used only in the Solder Paste Dispensing Tool
|
||||
if name.partition('_')[0] == 'Paste':
|
||||
self.tools_defaults_form.tools_solderpaste_group.pp_combo.addItem(name)
|
||||
continue
|
||||
|
||||
self.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb.addItem(name)
|
||||
# HPGL postprocessor is only for Geometry objects therefore it should not be in the Excellon Preferences
|
||||
if name == 'hpgl':
|
||||
continue
|
||||
|
||||
self.excellon_defaults_form.excellon_opt_group.pp_excellon_name_cb.addItem(name)
|
||||
|
||||
self.defaults = LoudDict()
|
||||
|
@ -486,6 +531,7 @@ class App(QtCore.QObject):
|
|||
"global_send_stats": True,
|
||||
"global_project_at_startup": False,
|
||||
"global_project_autohide": True,
|
||||
"global_app_level": 'b',
|
||||
|
||||
"global_gridx": 1.0,
|
||||
"global_gridy": 1.0,
|
||||
|
@ -531,6 +577,9 @@ class App(QtCore.QObject):
|
|||
"global_shell_shape": [500, 300], # Shape of the shell in pixels.
|
||||
"global_shell_at_startup": False, # Show the shell at startup.
|
||||
"global_recent_limit": 10, # Max. items in recent list.
|
||||
"global_compression_level": 3,
|
||||
"global_save_compressed": True,
|
||||
|
||||
"fit_key": 'V',
|
||||
"zoom_out_key": '-',
|
||||
"zoom_in_key": '=',
|
||||
|
@ -666,6 +715,7 @@ class App(QtCore.QObject):
|
|||
"tools_panelize_constrain": False,
|
||||
"tools_panelize_constrainx": 0.0,
|
||||
"tools_panelize_constrainy": 0.0,
|
||||
"tools_panelize_panel_type": 'gerber',
|
||||
|
||||
"tools_calc_vshape_tip_dia": 0.007874,
|
||||
"tools_calc_vshape_tip_angle": 30,
|
||||
|
@ -673,7 +723,36 @@ class App(QtCore.QObject):
|
|||
"tools_calc_electro_length": 10.0,
|
||||
"tools_calc_electro_width": 10.0,
|
||||
"tools_calc_electro_cdensity":13.0,
|
||||
"tools_calc_electro_growth": 10.0
|
||||
"tools_calc_electro_growth": 10.0,
|
||||
|
||||
"tools_transform_rotate": 90,
|
||||
"tools_transform_skew_x": 0.0,
|
||||
"tools_transform_skew_y": 0.0,
|
||||
"tools_transform_scale_x": 1.0,
|
||||
"tools_transform_scale_y": 1.0,
|
||||
"tools_transform_scale_link": True,
|
||||
"tools_transform_scale_reference": True,
|
||||
"tools_transform_offset_x": 0.0,
|
||||
"tools_transform_offset_y": 0.0,
|
||||
"tools_transform_mirror_reference": False,
|
||||
"tools_transform_mirror_point": (0, 0),
|
||||
|
||||
"tools_solderpaste_tools": "1.0, 0.3",
|
||||
"tools_solderpaste_new": 0.3,
|
||||
"tools_solderpaste_z_start": 0.005,
|
||||
"tools_solderpaste_z_dispense": 0.01,
|
||||
"tools_solderpaste_z_stop": 0.005,
|
||||
"tools_solderpaste_z_travel": 0.1,
|
||||
"tools_solderpaste_z_toolchange": 1.0,
|
||||
"tools_solderpaste_xy_toolchange": "0.0, 0.0",
|
||||
"tools_solderpaste_frxy": 3.0,
|
||||
"tools_solderpaste_frz": 3.0,
|
||||
"tools_solderpaste_frz_dispense": 1.0,
|
||||
"tools_solderpaste_speedfwd": 20,
|
||||
"tools_solderpaste_dwellfwd": 1,
|
||||
"tools_solderpaste_speedrev": 10,
|
||||
"tools_solderpaste_dwellrev": 1,
|
||||
"tools_solderpaste_pp": 'Paste_1'
|
||||
})
|
||||
|
||||
###############################
|
||||
|
@ -1069,7 +1148,6 @@ class App(QtCore.QObject):
|
|||
self.ui.menufilenewexc.triggered.connect(self.new_excellon_object)
|
||||
|
||||
self.ui.menufileopengerber.triggered.connect(self.on_fileopengerber)
|
||||
self.ui.menufileopengerber_follow.triggered.connect(self.on_fileopengerber_follow)
|
||||
self.ui.menufileopenexcellon.triggered.connect(self.on_fileopenexcellon)
|
||||
self.ui.menufileopengcode.triggered.connect(self.on_fileopengcode)
|
||||
self.ui.menufileopenproject.triggered.connect(self.on_file_openproject)
|
||||
|
@ -1129,7 +1207,7 @@ class App(QtCore.QObject):
|
|||
|
||||
self.ui.menuoptions_transform_flipx.triggered.connect(self.on_flipx)
|
||||
self.ui.menuoptions_transform_flipy.triggered.connect(self.on_flipy)
|
||||
|
||||
self.ui.menuoptions_view_source.triggered.connect(self.on_view_source)
|
||||
|
||||
self.ui.menuviewdisableall.triggered.connect(self.disable_all_plots)
|
||||
self.ui.menuviewdisableother.triggered.connect(self.disable_other_plots)
|
||||
|
@ -1156,6 +1234,8 @@ class App(QtCore.QObject):
|
|||
self.ui.menuprojectenable.triggered.connect(lambda: self.enable_plots(self.collection.get_selected()))
|
||||
self.ui.menuprojectdisable.triggered.connect(lambda: self.disable_plots(self.collection.get_selected()))
|
||||
self.ui.menuprojectgeneratecnc.triggered.connect(lambda: self.generate_cnc_job(self.collection.get_selected()))
|
||||
self.ui.menuprojectviewsource.triggered.connect(self.on_view_source)
|
||||
|
||||
self.ui.menuprojectcopy.triggered.connect(self.on_copy_object)
|
||||
self.ui.menuprojectedit.triggered.connect(self.object2editor)
|
||||
|
||||
|
@ -1262,7 +1342,7 @@ class App(QtCore.QObject):
|
|||
self.general_defaults_form.general_gui_group.wk_cb.currentIndexChanged.connect(self.on_workspace_modified)
|
||||
self.general_defaults_form.general_gui_group.workspace_cb.stateChanged.connect(self.on_workspace)
|
||||
|
||||
self.general_defaults_form.general_gui_group.layout_combo.activated.connect(self.on_layout)
|
||||
self.general_defaults_form.general_gui_set_group.layout_combo.activated.connect(self.on_layout)
|
||||
|
||||
# Modify G-CODE Plot Area TAB
|
||||
self.ui.code_editor.textChanged.connect(self.handleTextChanged)
|
||||
|
@ -1468,7 +1548,7 @@ class App(QtCore.QObject):
|
|||
if not factory_defaults:
|
||||
self.save_factory_defaults(silent=False)
|
||||
# ONLY AT FIRST STARTUP INIT THE GUI LAYOUT TO 'COMPACT'
|
||||
self.on_layout(layout='compact')
|
||||
self.on_layout(index=None, lay='compact')
|
||||
factory_file.close()
|
||||
|
||||
# and then make the factory_defaults.FlatConfig file read_only os it can't be modified after creation.
|
||||
|
@ -1544,13 +1624,16 @@ class App(QtCore.QObject):
|
|||
self.panelize_tool.install(icon=QtGui.QIcon('share/panel16.png'))
|
||||
|
||||
self.film_tool = Film(self)
|
||||
self.film_tool.install(icon=QtGui.QIcon('share/film16.png'), separator=True)
|
||||
self.film_tool.install(icon=QtGui.QIcon('share/film16.png'))
|
||||
|
||||
self.paste_tool = SolderPaste(self)
|
||||
self.paste_tool.install(icon=QtGui.QIcon('share/solderpastebis32.png'), separator=True)
|
||||
|
||||
self.move_tool = ToolMove(self)
|
||||
self.move_tool.install(icon=QtGui.QIcon('share/move16.png'), pos=self.ui.menuedit,
|
||||
before=self.ui.menueditorigin)
|
||||
|
||||
self.cutout_tool = ToolCutOut(self)
|
||||
self.cutout_tool = CutOut(self)
|
||||
self.cutout_tool.install(icon=QtGui.QIcon('share/cut16.png'), pos=self.ui.menutool,
|
||||
before=self.measurement_tool.menuAction)
|
||||
|
||||
|
@ -1634,6 +1717,10 @@ class App(QtCore.QObject):
|
|||
# store the Geometry Editor Toolbar visibility before entering in the Editor
|
||||
self.geo_editor.toolbar_old_state = True if self.ui.geo_edit_toolbar.isVisible() else False
|
||||
self.geo_editor.edit_fcgeometry(edited_object)
|
||||
|
||||
# we set the notebook to hidden
|
||||
self.ui.splitter.setSizes([0, 1])
|
||||
|
||||
# set call source to the Editor we go into
|
||||
self.call_source = 'geo_editor'
|
||||
|
||||
|
@ -1701,6 +1788,10 @@ class App(QtCore.QObject):
|
|||
self.inform.emit("[WARNING_NOTCL]Select a Geometry or Excellon Object to update.")
|
||||
return
|
||||
|
||||
# if notebook is hidden we show it
|
||||
if self.ui.splitter.sizes()[0] == 0:
|
||||
self.ui.splitter.setSizes([1, 1])
|
||||
|
||||
# restore the call_source to app
|
||||
self.call_source = 'app'
|
||||
|
||||
|
@ -3363,13 +3454,12 @@ class App(QtCore.QObject):
|
|||
self.general_defaults_form.general_gui_group.workspace_cb.setChecked(True)
|
||||
self.on_workspace()
|
||||
|
||||
def on_layout(self, layout=None):
|
||||
def on_layout(self, index, lay=None):
|
||||
self.report_usage("on_layout()")
|
||||
|
||||
if layout is None:
|
||||
current_layout= self.general_defaults_form.general_gui_group.layout_combo.get_value().lower()
|
||||
if lay:
|
||||
current_layout = lay
|
||||
else:
|
||||
current_layout = layout
|
||||
current_layout = self.general_defaults_form.general_gui_set_group.layout_combo.get_value().lower()
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
settings.setValue('layout', current_layout)
|
||||
|
@ -3588,34 +3678,46 @@ class App(QtCore.QObject):
|
|||
# work only if the notebook tab on focus is the Selected_Tab and only if the object is Geometry
|
||||
if notebook_widget_name == 'selected_tab':
|
||||
if str(type(self.collection.get_active())) == "<class 'FlatCAMObj.FlatCAMGeometry'>":
|
||||
tool_add_popup = FCInputDialog(title="New Tool ...",
|
||||
text='Enter a Tool Diameter:',
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
# Tool add works for Geometry only if Advanced is True in Preferences
|
||||
if self.defaults["global_advanced"] is True:
|
||||
tool_add_popup = FCInputDialog(title="New Tool ...",
|
||||
text='Enter a Tool Diameter:',
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.inform.emit(
|
||||
"[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
|
||||
return
|
||||
self.collection.get_active().on_tool_add(dia=float(val))
|
||||
else:
|
||||
self.inform.emit(
|
||||
"[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
|
||||
return
|
||||
self.collection.get_active().on_tool_add(dia=float(val))
|
||||
"[WARNING_NOTCL] Adding Tool cancelled ...")
|
||||
else:
|
||||
self.inform.emit(
|
||||
"[WARNING_NOTCL] Adding Tool cancelled ...")
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
msgbox.setText("Adding Tool works only when Advanced is checked.\n"
|
||||
"Go to Preferences -> General - Show Advanced Options.")
|
||||
msgbox.setWindowTitle("Tool adding ...")
|
||||
msgbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
|
||||
msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||||
msgbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
|
||||
msgbox.exec_()
|
||||
|
||||
# work only if the notebook tab on focus is the Tools_Tab
|
||||
if notebook_widget_name == 'tool_tab':
|
||||
tool_widget = self.ui.tool_scroll_area.widget().objectName()
|
||||
|
||||
tool_add_popup = FCInputDialog(title="New Tool ...",
|
||||
text='Enter a Tool Diameter:',
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
|
||||
# and only if the tool is NCC Tool
|
||||
if tool_widget == self.ncclear_tool.toolName:
|
||||
tool_add_popup = FCInputDialog(title="New Tool ...",
|
||||
text='Enter a Tool Diameter:',
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.inform.emit(
|
||||
|
@ -3627,12 +3729,6 @@ class App(QtCore.QObject):
|
|||
"[WARNING_NOTCL] Adding Tool cancelled ...")
|
||||
# and only if the tool is Paint Area Tool
|
||||
elif tool_widget == self.paint_tool.toolName:
|
||||
tool_add_popup = FCInputDialog(title="New Tool ...",
|
||||
text='Enter a Tool Diameter:',
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.inform.emit(
|
||||
|
@ -3642,6 +3738,18 @@ class App(QtCore.QObject):
|
|||
else:
|
||||
self.inform.emit(
|
||||
"[WARNING_NOTCL] Adding Tool cancelled ...")
|
||||
# and only if the tool is Solder Paste Dispensing Tool
|
||||
elif tool_widget == self.paste_tool.toolName:
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.inform.emit(
|
||||
"[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
|
||||
return
|
||||
self.paste_tool.on_tool_add(dia=float(val))
|
||||
else:
|
||||
self.inform.emit(
|
||||
"[WARNING_NOTCL] Adding Tool cancelled ...")
|
||||
|
||||
|
||||
# It's meant to delete tools in tool tables via a 'Delete' shortcut key but only if certain conditions are met
|
||||
# See description bellow.
|
||||
|
@ -3665,6 +3773,9 @@ class App(QtCore.QObject):
|
|||
elif tool_widget == self.paint_tool.toolName:
|
||||
self.paint_tool.on_tool_delete()
|
||||
|
||||
# and only if the tool is Solder Paste Dispensing Tool
|
||||
elif tool_widget == self.paste_tool.toolName:
|
||||
self.paste_tool.on_tool_delete()
|
||||
else:
|
||||
self.on_delete()
|
||||
|
||||
|
@ -3934,7 +4045,7 @@ class App(QtCore.QObject):
|
|||
obj.mirror('X', [px, py])
|
||||
obj.plot()
|
||||
self.object_changed.emit(obj)
|
||||
|
||||
self.inform.emit("[success] Flip on Y axis done.")
|
||||
except Exception as e:
|
||||
self.inform.emit("[ERROR_NOTCL] Due of %s, Flip action was not executed." % str(e))
|
||||
return
|
||||
|
@ -3974,7 +4085,7 @@ class App(QtCore.QObject):
|
|||
obj.mirror('Y', [px, py])
|
||||
obj.plot()
|
||||
self.object_changed.emit(obj)
|
||||
|
||||
self.inform.emit("[success] Flip on X axis done.")
|
||||
except Exception as e:
|
||||
self.inform.emit("[ERROR_NOTCL] Due of %s, Flip action was not executed." % str(e))
|
||||
return
|
||||
|
@ -3993,7 +4104,8 @@ class App(QtCore.QObject):
|
|||
else:
|
||||
if silent is False:
|
||||
rotatebox = FCInputDialog(title="Transform", text="Enter the Angle value:",
|
||||
min=-360, max=360, decimals=3)
|
||||
min=-360, max=360, decimals=4,
|
||||
init_val=float(self.defaults['tools_transform_rotate']))
|
||||
num, ok = rotatebox.get_value()
|
||||
else:
|
||||
num = preset
|
||||
|
@ -4018,9 +4130,10 @@ class App(QtCore.QObject):
|
|||
py = 0.5 * (yminimal + ymaximal)
|
||||
|
||||
for sel_obj in obj_list:
|
||||
sel_obj.rotate(-num, point=(px, py))
|
||||
sel_obj.rotate(-float(num), point=(px, py))
|
||||
sel_obj.plot()
|
||||
self.object_changed.emit(sel_obj)
|
||||
self.inform.emit("[success] Rotation done.")
|
||||
except Exception as e:
|
||||
self.inform.emit("[ERROR_NOTCL] Due of %s, rotation movement was not executed." % str(e))
|
||||
return
|
||||
|
@ -4036,7 +4149,8 @@ class App(QtCore.QObject):
|
|||
self.inform.emit("[WARNING_NOTCL] No object selected to Skew/Shear on X axis.")
|
||||
else:
|
||||
skewxbox = FCInputDialog(title="Transform", text="Enter the Angle value:",
|
||||
min=-360, max=360, decimals=3)
|
||||
min=-360, max=360, decimals=4,
|
||||
init_val=float(self.defaults['tools_transform_skew_x']))
|
||||
num, ok = skewxbox.get_value()
|
||||
if ok:
|
||||
# first get a bounding box to fit all
|
||||
|
@ -4053,6 +4167,7 @@ class App(QtCore.QObject):
|
|||
obj.skew(num, 0, point=(xminimal, yminimal))
|
||||
obj.plot()
|
||||
self.object_changed.emit(obj)
|
||||
self.inform.emit("[success] Skew on X axis done.")
|
||||
|
||||
def on_skewy(self):
|
||||
self.report_usage("on_skewy()")
|
||||
|
@ -4065,7 +4180,8 @@ class App(QtCore.QObject):
|
|||
self.inform.emit("[WARNING_NOTCL] No object selected to Skew/Shear on Y axis.")
|
||||
else:
|
||||
skewybox = FCInputDialog(title="Transform", text="Enter the Angle value:",
|
||||
min=-360, max=360, decimals=3)
|
||||
min=-360, max=360, decimals=4,
|
||||
init_val=float(self.defaults['tools_transform_skew_y']))
|
||||
num, ok = skewybox.get_value()
|
||||
if ok:
|
||||
# first get a bounding box to fit all
|
||||
|
@ -4082,6 +4198,7 @@ class App(QtCore.QObject):
|
|||
obj.skew(0, num, point=(xminimal, yminimal))
|
||||
obj.plot()
|
||||
self.object_changed.emit(obj)
|
||||
self.inform.emit("[success] Skew on Y axis done.")
|
||||
|
||||
def delete_first_selected(self):
|
||||
# Keep this for later
|
||||
|
@ -4728,10 +4845,46 @@ class App(QtCore.QObject):
|
|||
self.on_file_exportexcellon()
|
||||
elif type(obj) == FlatCAMCNCjob:
|
||||
obj.on_exportgcode_button_click()
|
||||
elif type(obj) == FlatCAMGerber:
|
||||
self.on_file_exportgerber()
|
||||
def on_view_source(self):
|
||||
|
||||
try:
|
||||
obj = self.collection.get_active()
|
||||
except:
|
||||
self.inform.emit("[WARNING_NOTCL] Select an Gerber or Excellon file to view it's source.")
|
||||
|
||||
# add the tab if it was closed
|
||||
self.ui.plot_tab_area.addTab(self.ui.cncjob_tab, "Code Editor")
|
||||
# first clear previous text in text editor (if any)
|
||||
self.ui.code_editor.clear()
|
||||
|
||||
# Switch plot_area to CNCJob tab
|
||||
self.ui.plot_tab_area.setCurrentWidget(self.ui.cncjob_tab)
|
||||
|
||||
# then append the text from GCode to the text editor
|
||||
file = StringIO(obj.source_file)
|
||||
try:
|
||||
for line in file:
|
||||
proc_line = str(line).strip('\n')
|
||||
self.ui.code_editor.append(proc_line)
|
||||
except Exception as e:
|
||||
log.debug('App.on_view_source() -->%s' % str(e))
|
||||
self.inform.emit('[ERROR]App.on_view_source() -->%s' % str(e))
|
||||
return
|
||||
|
||||
self.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
|
||||
|
||||
self.handleTextChanged()
|
||||
self.ui.show()
|
||||
|
||||
# if type(obj) == FlatCAMGerber:
|
||||
# self.on_file_exportdxf()
|
||||
# elif type(obj) == FlatCAMExcellon:
|
||||
# self.on_file_exportexcellon()
|
||||
|
||||
def obj_move(self):
|
||||
self.report_usage("obj_move()")
|
||||
|
||||
self.move_tool.run()
|
||||
|
||||
def on_fileopengerber(self):
|
||||
|
@ -4770,42 +4923,6 @@ class App(QtCore.QObject):
|
|||
self.worker_task.emit({'fcn': self.open_gerber,
|
||||
'params': [filename]})
|
||||
|
||||
def on_fileopengerber_follow(self):
|
||||
"""
|
||||
File menu callback for opening a Gerber.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
self.report_usage("on_fileopengerber_follow")
|
||||
App.log.debug("on_fileopengerber_follow()")
|
||||
_filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 *.gko " \
|
||||
"*.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil *.grb" \
|
||||
"*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb *.pho *.gdo *.art *.gbd);;" \
|
||||
"Protel Files (*.gtl *.gbl *.gts *.gbs *.gto *.gbo *.gtp *.gbp *.gml *.gm1 *.gm3 *.gko);;" \
|
||||
"Eagle Files (*.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil);;" \
|
||||
"OrCAD Files (*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb);;" \
|
||||
"Allegro Files (*.art);;" \
|
||||
"Mentor Files (*.pho *.gdo);;" \
|
||||
"All Files (*.*)"
|
||||
try:
|
||||
filename, _ = QtWidgets.QFileDialog.getOpenFileName(caption="Open Gerber with Follow",
|
||||
directory=self.get_last_folder(), filter=_filter_)
|
||||
except TypeError:
|
||||
filename, _ = QtWidgets.QFileDialog.getOpenFileName(caption="Open Gerber with Follow", 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)
|
||||
follow = True
|
||||
|
||||
if filename == "":
|
||||
self.inform.emit("[WARNING_NOTCL]Open Gerber-Follow cancelled.")
|
||||
else:
|
||||
self.worker_task.emit({'fcn': self.open_gerber,
|
||||
'params': [filename, follow]})
|
||||
|
||||
def on_fileopenexcellon(self):
|
||||
"""
|
||||
File menu callback for opening an Excellon file.
|
||||
|
@ -4975,6 +5092,45 @@ class App(QtCore.QObject):
|
|||
write_png(filename, data)
|
||||
self.file_saved.emit("png", filename)
|
||||
|
||||
def on_file_exportgerber(self):
|
||||
"""
|
||||
Callback for menu item File->Export SVG.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.report_usage("on_file_exportgerber")
|
||||
App.log.debug("on_file_exportgerber()")
|
||||
|
||||
obj = self.collection.get_active()
|
||||
if obj is None:
|
||||
self.inform.emit("[WARNING_NOTCL] No object selected. Please Select an Gerber object to export.")
|
||||
return
|
||||
|
||||
# Check for more compatible types and add as required
|
||||
if not isinstance(obj, FlatCAMGerber):
|
||||
self.inform.emit("[ERROR_NOTCL] Failed. Only Gerber objects can be saved as Gerber files...")
|
||||
return
|
||||
|
||||
name = self.collection.get_active().options["name"]
|
||||
|
||||
filter = "Gerber File (*.GBR);;Gerber File (*.GRB);;All Files (*.*)"
|
||||
try:
|
||||
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption="Export Gerber",
|
||||
directory=self.get_last_save_folder() + '/' + name,
|
||||
filter=filter)
|
||||
except TypeError:
|
||||
filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export Gerber", filter=filter)
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
if filename == "":
|
||||
self.inform.emit("[WARNING_NOTCL]Export Gerber cancelled.")
|
||||
return
|
||||
else:
|
||||
self.export_gerber(name, filename)
|
||||
self.file_saved.emit("Gerber", filename)
|
||||
|
||||
def on_file_exportexcellon(self):
|
||||
"""
|
||||
Callback for menu item File->Export SVG.
|
||||
|
@ -5213,15 +5369,15 @@ class App(QtCore.QObject):
|
|||
except IOError:
|
||||
exists = False
|
||||
|
||||
msg = "Project file exists. Overwrite?"
|
||||
if exists:
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
msgbox.setInformativeText(msg)
|
||||
msgbox.setStandardButtons(QtWidgets.QMessageBox.Cancel |QtWidgets.QMessageBox.Ok)
|
||||
msgbox.setDefaultButton(QtWidgets.QMessageBox.Cancel)
|
||||
result = msgbox.exec_()
|
||||
if result ==QtWidgets.QMessageBox.Cancel:
|
||||
return
|
||||
# msg = "Project file exists. Overwrite?"
|
||||
# if exists:
|
||||
# msgbox = QtWidgets.QMessageBox()
|
||||
# msgbox.setInformativeText(msg)
|
||||
# msgbox.setStandardButtons(QtWidgets.QMessageBox.Cancel |QtWidgets.QMessageBox.Ok)
|
||||
# msgbox.setDefaultButton(QtWidgets.QMessageBox.Cancel)
|
||||
# result = msgbox.exec_()
|
||||
# if result ==QtWidgets.QMessageBox.Cancel:
|
||||
# return
|
||||
|
||||
if thread is True:
|
||||
self.worker_task.emit({'fcn': self.save_project,
|
||||
|
@ -5527,9 +5683,38 @@ class App(QtCore.QObject):
|
|||
else:
|
||||
make_black_film()
|
||||
|
||||
def export_gerber(self, obj_name, filename, use_thread=True):
|
||||
"""
|
||||
Exports a Gerber Object to an Gerber file.
|
||||
|
||||
:param filename: Path to the Gerber file to save to.
|
||||
:return:
|
||||
"""
|
||||
self.report_usage("export_gerber()")
|
||||
|
||||
if filename is None:
|
||||
filename = self.defaults["global_last_save_folder"]
|
||||
|
||||
self.log.debug("export_gerber()")
|
||||
|
||||
obj = self.collection.get_by_name(obj_name)
|
||||
|
||||
file_string = StringIO(obj.source_file)
|
||||
time_string = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
|
||||
|
||||
with open(filename, 'w') as file:
|
||||
file.writelines('G04*\n')
|
||||
file.writelines('G04 GERBER (RE)GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' %
|
||||
(str(self.version), str(self.version_date)))
|
||||
file.writelines('G04 Filename: %s*\n' % str(obj_name))
|
||||
file.writelines('G04 Created on : %s*\n' % time_string)
|
||||
|
||||
for line in file_string:
|
||||
file.writelines(line)
|
||||
|
||||
def export_excellon(self, obj_name, filename, use_thread=True):
|
||||
"""
|
||||
Exports a Geometry Object to an Excellon file.
|
||||
Exports a Excellon Object to an Excellon file.
|
||||
|
||||
:param filename: Path to the Excellon file to save to.
|
||||
:return:
|
||||
|
@ -5834,7 +6019,7 @@ class App(QtCore.QObject):
|
|||
self.inform.emit("[success] Opened: " + filename)
|
||||
self.progress.emit(100)
|
||||
|
||||
def open_gerber(self, filename, follow=False, outname=None):
|
||||
def open_gerber(self, filename, outname=None):
|
||||
"""
|
||||
Opens a Gerber file, parses it and creates a new object for
|
||||
it in the program. Thread-safe.
|
||||
|
@ -5858,7 +6043,7 @@ class App(QtCore.QObject):
|
|||
# Opening the file happens here
|
||||
self.progress.emit(30)
|
||||
try:
|
||||
gerber_obj.parse_file(filename, follow=follow)
|
||||
gerber_obj.parse_file(filename)
|
||||
except IOError:
|
||||
app_obj.inform.emit("[ERROR_NOTCL] Failed to open file: " + filename)
|
||||
app_obj.progress.emit(0)
|
||||
|
@ -5886,10 +6071,7 @@ class App(QtCore.QObject):
|
|||
# Further parsing
|
||||
self.progress.emit(70) # TODO: Note the mixture of self and app_obj used here
|
||||
|
||||
if follow is False:
|
||||
App.log.debug("open_gerber()")
|
||||
else:
|
||||
App.log.debug("open_gerber() with 'follow' attribute")
|
||||
App.log.debug("open_gerber()")
|
||||
|
||||
with self.proc_container.new("Opening Gerber") as proc:
|
||||
|
||||
|
@ -6063,7 +6245,7 @@ class App(QtCore.QObject):
|
|||
"""
|
||||
App.log.debug("Opening project: " + filename)
|
||||
|
||||
# Open and parse
|
||||
# Open and parse an uncompressed Project file
|
||||
try:
|
||||
f = open(filename, 'r')
|
||||
except IOError:
|
||||
|
@ -6077,7 +6259,16 @@ class App(QtCore.QObject):
|
|||
App.log.error("Failed to parse project file: %s" % filename)
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to parse project file: %s" % filename)
|
||||
f.close()
|
||||
return
|
||||
|
||||
# Open and parse a compressed Project file
|
||||
try:
|
||||
with lzma.open(filename) as f:
|
||||
file_content = f.read().decode('utf-8')
|
||||
d = json.loads(file_content, object_hook=dict2obj)
|
||||
except IOError:
|
||||
App.log.error("Failed to open project file: %s" % filename)
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to open project file: %s" % filename)
|
||||
return
|
||||
|
||||
self.file_opened.emit("project", filename)
|
||||
|
||||
|
@ -6101,7 +6292,6 @@ class App(QtCore.QObject):
|
|||
obj_inst.from_dict(obj)
|
||||
App.log.debug(obj['kind'] + ": " + obj['options']['name'])
|
||||
self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True)
|
||||
|
||||
self.plot_all()
|
||||
self.inform.emit("[success] Project loaded from: " + filename)
|
||||
|
||||
|
@ -6160,6 +6350,12 @@ class App(QtCore.QObject):
|
|||
self.defaults["global_def_win_w"],
|
||||
self.defaults["global_def_win_h"])
|
||||
self.ui.splitter.setSizes([self.defaults["def_notebook_width"], 0])
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("maximized_gui"):
|
||||
maximized_ui = settings.value('maximized_gui', type=bool)
|
||||
if maximized_ui is True:
|
||||
self.ui.showMaximized()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
@ -6174,7 +6370,7 @@ class App(QtCore.QObject):
|
|||
for obj in self.collection.get_list():
|
||||
def worker_task(obj):
|
||||
with self.proc_container.new("Plotting"):
|
||||
obj.plot()
|
||||
obj.plot(kind=self.defaults["cncjob_plot_kind"])
|
||||
if zoom:
|
||||
self.object_plotted.emit(obj)
|
||||
|
||||
|
@ -6793,37 +6989,45 @@ The normal flow when working in FlatCAM is the following:</span></p>
|
|||
"options": self.options,
|
||||
"version": self.version}
|
||||
|
||||
# Open file
|
||||
try:
|
||||
f = open(filename, 'w')
|
||||
except IOError:
|
||||
App.log.error("[ERROR] Failed to open file for saving: %s", filename)
|
||||
return
|
||||
|
||||
# Write
|
||||
json.dump(d, f, default=to_dict, indent=2, sort_keys=True)
|
||||
f.close()
|
||||
|
||||
# verification of the saved project
|
||||
# Open and parse
|
||||
try:
|
||||
saved_f = open(filename, 'r')
|
||||
except IOError:
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to verify project file: %s. Retry to save it." % filename)
|
||||
return
|
||||
|
||||
try:
|
||||
saved_d = json.load(saved_f, object_hook=dict2obj)
|
||||
except:
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to parse saved project file: %s. Retry to save it." % filename)
|
||||
f.close()
|
||||
return
|
||||
saved_f.close()
|
||||
|
||||
if 'version' in saved_d:
|
||||
if self.defaults["global_save_compressed"] is True:
|
||||
with lzma.open(filename, "w", preset=int(self.defaults['global_compression_level'])) as f:
|
||||
g = json.dumps(d, default=to_dict, indent=2, sort_keys=True).encode('utf-8')
|
||||
# # Write
|
||||
f.write(g)
|
||||
self.inform.emit("[success] Project saved to: %s" % filename)
|
||||
else:
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to save project file: %s. Retry to save it." % filename)
|
||||
# Open file
|
||||
try:
|
||||
f = open(filename, 'w')
|
||||
except IOError:
|
||||
App.log.error("[ERROR] Failed to open file for saving: %s", filename)
|
||||
return
|
||||
|
||||
# Write
|
||||
json.dump(d, f, default=to_dict, indent=2, sort_keys=True)
|
||||
f.close()
|
||||
|
||||
# verification of the saved project
|
||||
# Open and parse
|
||||
try:
|
||||
saved_f = open(filename, 'r')
|
||||
except IOError:
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to verify project file: %s. Retry to save it." % filename)
|
||||
return
|
||||
|
||||
try:
|
||||
saved_d = json.load(saved_f, object_hook=dict2obj)
|
||||
except:
|
||||
self.inform.emit(
|
||||
"[ERROR_NOTCL] Failed to parse saved project file: %s. Retry to save it." % filename)
|
||||
f.close()
|
||||
return
|
||||
saved_f.close()
|
||||
|
||||
if 'version' in saved_d:
|
||||
self.inform.emit("[success] Project saved to: %s" % filename)
|
||||
else:
|
||||
self.inform.emit("[ERROR_NOTCL] Failed to save project file: %s. Retry to save it." % filename)
|
||||
|
||||
def on_options_app2project(self):
|
||||
"""
|
||||
|
|
1613
FlatCAMEditor.py
1613
FlatCAMEditor.py
File diff suppressed because it is too large
Load Diff
761
FlatCAMGUI.py
761
FlatCAMGUI.py
File diff suppressed because it is too large
Load Diff
229
FlatCAMObj.py
229
FlatCAMObj.py
|
@ -106,7 +106,12 @@ class FlatCAMObj(QtCore.QObject):
|
|||
if attr == 'options':
|
||||
self.options.update(d[attr])
|
||||
else:
|
||||
setattr(self, attr, d[attr])
|
||||
try:
|
||||
setattr(self, attr, d[attr])
|
||||
except KeyError:
|
||||
log.debug("FlatCAMObj.from_dict() --> KeyError: %s. Means that we are loading an old project that don't"
|
||||
"have all attributes in the latest FlatCAM." % str(attr))
|
||||
pass
|
||||
|
||||
def on_options_change(self, key):
|
||||
# Update form on programmatically options change
|
||||
|
@ -366,9 +371,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
|
||||
if grb_final.solid_geometry is None:
|
||||
grb_final.solid_geometry = []
|
||||
grb_final.follow_geometry = []
|
||||
|
||||
if type(grb_final.solid_geometry) is not list:
|
||||
grb_final.solid_geometry = [grb_final.solid_geometry]
|
||||
grb_final.follow_geometry = [grb_final.follow_geometry]
|
||||
|
||||
for grb in grb_list:
|
||||
for option in grb.options:
|
||||
|
@ -384,8 +391,10 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
else: # If not list, just append
|
||||
for geos in grb.solid_geometry:
|
||||
grb_final.solid_geometry.append(geos)
|
||||
grb_final.follow_geometry.append(geos)
|
||||
|
||||
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
|
||||
grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry)
|
||||
|
||||
def __init__(self, name):
|
||||
Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"]))
|
||||
|
@ -413,15 +422,15 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
# type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors)
|
||||
self.iso_type = 2
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from predecessors.
|
||||
self.ser_attrs += ['options', 'kind']
|
||||
|
||||
self.multigeo = False
|
||||
|
||||
self.follow = False
|
||||
|
||||
self.apertures_row = 0
|
||||
|
||||
# store the source file here
|
||||
self.source_file = ""
|
||||
|
||||
# assert isinstance(self.ui, GerberObjectUI)
|
||||
# self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
# self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
|
||||
|
@ -431,6 +440,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
# self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
|
||||
# self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from predecessors.
|
||||
self.ser_attrs += ['options', 'kind']
|
||||
|
||||
def set_ui(self, ui):
|
||||
"""
|
||||
Maps options with GUI inputs.
|
||||
|
@ -474,6 +488,20 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
|
||||
self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
|
||||
self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change)
|
||||
self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click)
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
if self.app.defaults["global_app_level"] == 'b':
|
||||
self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
|
||||
self.ui.apertures_table_label.hide()
|
||||
self.ui.aperture_table_visibility_cb.hide()
|
||||
self.ui.milling_type_label.hide()
|
||||
self.ui.milling_type_radio.hide()
|
||||
self.ui.generate_ext_iso_button.hide()
|
||||
self.ui.generate_int_iso_button.hide()
|
||||
|
||||
else:
|
||||
self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
|
||||
|
||||
self.build_ui()
|
||||
|
||||
|
@ -654,7 +682,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
|
||||
def on_int_iso_button_click(self, *args):
|
||||
|
||||
if self.ui.follow_cb.get_value() == True:
|
||||
if self.ui.follow_cb.get_value() is True:
|
||||
obj = self.app.collection.get_active()
|
||||
obj.follow()
|
||||
# in the end toggle the visibility of the origin object so we can see the generated Geometry
|
||||
|
@ -666,9 +694,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
|
||||
def on_iso_button_click(self, *args):
|
||||
|
||||
if self.ui.follow_cb.get_value() == True:
|
||||
if self.ui.follow_cb.get_value() is True:
|
||||
obj = self.app.collection.get_active()
|
||||
obj.follow()
|
||||
obj.follow_geo()
|
||||
# in the end toggle the visibility of the origin object so we can see the generated Geometry
|
||||
obj.ui.plot_cb.toggle()
|
||||
else:
|
||||
|
@ -676,7 +704,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
self.read_form()
|
||||
self.isolate()
|
||||
|
||||
def follow(self, outname=None):
|
||||
def follow_geo(self, outname=None):
|
||||
"""
|
||||
Creates a geometry object "following" the gerber paths.
|
||||
|
||||
|
@ -694,7 +722,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
def follow_init(follow_obj, app):
|
||||
# Propagate options
|
||||
follow_obj.options["cnctooldia"] = float(self.options["isotooldia"])
|
||||
follow_obj.solid_geometry = self.solid_geometry
|
||||
follow_obj.solid_geometry = self.follow_geometry
|
||||
|
||||
# TODO: Do something if this is None. Offer changing name?
|
||||
try:
|
||||
|
@ -703,7 +731,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
return "Operation failed: %s" % str(e)
|
||||
|
||||
def isolate(self, iso_type=None, dia=None, passes=None, overlap=None,
|
||||
outname=None, combine=None, milling_type=None):
|
||||
outname=None, combine=None, milling_type=None, follow=None):
|
||||
"""
|
||||
Creates an isolation routing geometry object in the project.
|
||||
|
||||
|
@ -714,6 +742,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
:param outname: Base name of the output object
|
||||
:return: None
|
||||
"""
|
||||
|
||||
|
||||
if dia is None:
|
||||
dia = float(self.options["isotooldia"])
|
||||
if passes is None:
|
||||
|
@ -734,7 +764,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
base_name = self.options["name"] + "_iso"
|
||||
base_name = outname or base_name
|
||||
|
||||
def generate_envelope(offset, invert, envelope_iso_type=2):
|
||||
def generate_envelope(offset, invert, envelope_iso_type=2, follow=None):
|
||||
# isolation_geometry produces an envelope that is going on the left of the geometry
|
||||
# (the copper features). To leave the least amount of burrs on the features
|
||||
# the tool needs to travel on the right side of the features (this is called conventional milling)
|
||||
|
@ -742,7 +772,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
# the other passes overlap preceding ones and cut the left over copper. It is better for them
|
||||
# to cut on the right side of the left over copper i.e on the left side of the features.
|
||||
try:
|
||||
geom = self.isolation_geometry(offset, iso_type=envelope_iso_type)
|
||||
geom = self.isolation_geometry(offset, iso_type=envelope_iso_type, follow=follow)
|
||||
except Exception as e:
|
||||
log.debug(str(e))
|
||||
return 'fail'
|
||||
|
@ -782,9 +812,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
# if milling type is climb then the move is counter-clockwise around features
|
||||
if milling_type == 'cl':
|
||||
# geom = generate_envelope (offset, i == 0)
|
||||
geom = generate_envelope(iso_offset, 1, envelope_iso_type=self.iso_type)
|
||||
geom = generate_envelope(iso_offset, 1, envelope_iso_type=self.iso_type, follow=follow)
|
||||
else:
|
||||
geom = generate_envelope(iso_offset, 0, envelope_iso_type=self.iso_type)
|
||||
geom = generate_envelope(iso_offset, 0, envelope_iso_type=self.iso_type, follow=follow)
|
||||
geo_obj.solid_geometry.append(geom)
|
||||
|
||||
# detect if solid_geometry is empty and this require list flattening which is "heavy"
|
||||
|
@ -834,9 +864,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
# if milling type is climb then the move is counter-clockwise around features
|
||||
if milling_type == 'cl':
|
||||
# geo_obj.solid_geometry = generate_envelope(offset, i == 0)
|
||||
geo_obj.solid_geometry = generate_envelope(offset, 1, envelope_iso_type=self.iso_type)
|
||||
geo_obj.solid_geometry = generate_envelope(offset, 1, envelope_iso_type=self.iso_type,
|
||||
follow=follow)
|
||||
else:
|
||||
geo_obj.solid_geometry = generate_envelope(offset, 0, envelope_iso_type=self.iso_type)
|
||||
geo_obj.solid_geometry = generate_envelope(offset, 0, envelope_iso_type=self.iso_type,
|
||||
follow=follow)
|
||||
|
||||
# detect if solid_geometry is empty and this require list flattening which is "heavy"
|
||||
# or just looking in the lists (they are one level depth) and if any is not empty
|
||||
|
@ -876,6 +908,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
self.read_form_item('multicolored')
|
||||
self.plot()
|
||||
|
||||
def on_follow_cb_click(self):
|
||||
if self.muted_ui:
|
||||
return
|
||||
self.plot()
|
||||
|
||||
def on_aperture_table_visibility_change(self):
|
||||
if self.ui.aperture_table_visibility_cb.isChecked():
|
||||
self.ui.apertures_table.setVisible(True)
|
||||
|
@ -921,7 +958,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
else:
|
||||
face_color = self.app.defaults['global_plot_fill']
|
||||
|
||||
geometry = self.solid_geometry
|
||||
# if the Follow Geometry checkbox is checked then plot only the follow geometry
|
||||
if self.ui.follow_cb.get_value():
|
||||
geometry = self.follow_geometry
|
||||
else:
|
||||
geometry = self.solid_geometry
|
||||
|
||||
# Make sure geometry is iterable.
|
||||
try:
|
||||
|
@ -941,6 +982,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
self.add_shape(shape=g, color=color,
|
||||
face_color=random_color() if self.options['multicolored']
|
||||
else face_color, visible=self.options['plot'])
|
||||
elif type(g) == Point:
|
||||
pass
|
||||
else:
|
||||
for el in g:
|
||||
self.add_shape(shape=el, color=color,
|
||||
|
@ -951,6 +994,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
if type(g) == Polygon or type(g) == LineString:
|
||||
self.add_shape(shape=g, color=random_color() if self.options['multicolored'] else 'black',
|
||||
visible=self.options['plot'])
|
||||
elif type(g) == Point:
|
||||
pass
|
||||
else:
|
||||
for el in g:
|
||||
self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black',
|
||||
|
@ -1076,11 +1121,6 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
# dict to hold the tool number as key and tool offset as value
|
||||
self.tool_offset ={}
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from predecessors.
|
||||
self.ser_attrs += ['options', 'kind']
|
||||
|
||||
# variable to store the total amount of drills per job
|
||||
self.tot_drill_cnt = 0
|
||||
self.tool_row = 0
|
||||
|
@ -1092,8 +1132,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
# variable to store the distance travelled
|
||||
self.travel_distance = 0.0
|
||||
|
||||
# store the source file here
|
||||
self.source_file = ""
|
||||
|
||||
self.multigeo = False
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from predecessors.
|
||||
self.ser_attrs += ['options', 'kind']
|
||||
|
||||
@staticmethod
|
||||
def merge(exc_list, exc_final):
|
||||
"""
|
||||
|
@ -1522,6 +1570,24 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
dia = float('%.3f' % float(value['C']))
|
||||
self.tool_offset[dia] = t_default_offset
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
if self.app.defaults["global_app_level"] == 'b':
|
||||
self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
|
||||
|
||||
self.ui.tools_table.setColumnHidden(4, True)
|
||||
self.ui.estartz_label.hide()
|
||||
self.ui.estartz_entry.hide()
|
||||
self.ui.eendz_label.hide()
|
||||
self.ui.eendz_entry.hide()
|
||||
self.ui.feedrate_rapid_label.hide()
|
||||
self.ui.feedrate_rapid_entry.hide()
|
||||
self.ui.pdepth_label.hide()
|
||||
self.ui.pdepth_entry.hide()
|
||||
self.ui.feedrate_probe_label.hide()
|
||||
self.ui.feedrate_probe_entry.hide()
|
||||
else:
|
||||
self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
|
||||
|
||||
assert isinstance(self.ui, ExcellonObjectUI), \
|
||||
"Expected a ExcellonObjectUI, got %s" % type(self.ui)
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
|
@ -1989,8 +2055,14 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
tools = self.get_selected_tools_list()
|
||||
|
||||
if len(tools) == 0:
|
||||
self.app.inform.emit("[ERROR_NOTCL]Please select one or more tools from the list and try again.")
|
||||
return
|
||||
# if there is a single tool in the table (remember that the last 2 rows are for totals and do not count in
|
||||
# tool number) it means that there are 3 rows (1 tool and 2 totals).
|
||||
# in this case regardless of the selection status of that tool, use it.
|
||||
if self.ui.tools_table.rowCount() == 3:
|
||||
tools.append(self.ui.tools_table.item(0, 0).text())
|
||||
else:
|
||||
self.app.inform.emit("[ERROR_NOTCL]Please select one or more tools from the list and try again.")
|
||||
return
|
||||
|
||||
xmin = self.options['xmin']
|
||||
ymin = self.options['ymin']
|
||||
|
@ -2462,8 +2534,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
# flag to store if the V-Shape tool is selected in self.ui.geo_tools_table
|
||||
self.v_tool_type = None
|
||||
|
||||
# flag to store if the Geometry is type 'multi-geometry' meaning that each tool has it's own geometry
|
||||
# the default value is False
|
||||
self.multigeo = False
|
||||
|
||||
# flag to store if the geometry is part of a special group of geometries that can't be processed by the default
|
||||
# engine of FlatCAM. Most likely are generated by some of tools and are special cases of geometries.
|
||||
self. special_group = None
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from predecessors.
|
||||
|
@ -2732,6 +2810,30 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
self.ui.geo_tools_table.addContextMenu(
|
||||
"Delete", lambda: self.on_tool_delete(all=None), icon=QtGui.QIcon("share/delete32.png"))
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
if self.app.defaults["global_app_level"] == 'b':
|
||||
self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
|
||||
|
||||
self.ui.geo_tools_table.setColumnHidden(2, True)
|
||||
self.ui.geo_tools_table.setColumnHidden(3, True)
|
||||
self.ui.geo_tools_table.setColumnHidden(4, True)
|
||||
self.ui.addtool_entry_lbl.hide()
|
||||
self.ui.addtool_entry.hide()
|
||||
self.ui.addtool_btn.hide()
|
||||
self.ui.copytool_btn.hide()
|
||||
self.ui.deltool_btn.hide()
|
||||
self.ui.endzlabel.hide()
|
||||
self.ui.gendz_entry.hide()
|
||||
self.ui.fr_rapidlabel.hide()
|
||||
self.ui.cncfeedrate_rapid_entry.hide()
|
||||
self.ui.extracut_cb.hide()
|
||||
self.ui.pdepth_label.hide()
|
||||
self.ui.pdepth_entry.hide()
|
||||
self.ui.feedrate_probe_label.hide()
|
||||
self.ui.feedrate_probe_entry.hide()
|
||||
else:
|
||||
self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
|
||||
|
||||
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
|
||||
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
|
||||
self.ui.paint_tool_button.clicked.connect(self.app.paint_tool.run)
|
||||
|
@ -2832,8 +2934,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
self.ui.grid3.itemAt(i).widget().currentIndexChanged.disconnect()
|
||||
|
||||
if isinstance(self.ui.grid3.itemAt(i).widget(), LengthEntry) or \
|
||||
isinstance(self.ui.grid3.itemAt(i), IntEntry) or \
|
||||
isinstance(self.ui.grid3.itemAt(i), FCEntry):
|
||||
isinstance(self.ui.grid3.itemAt(i).widget(), IntEntry) or \
|
||||
isinstance(self.ui.grid3.itemAt(i).widget(), FCEntry):
|
||||
self.ui.grid3.itemAt(i).widget().editingFinished.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
@ -3544,6 +3646,17 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
|
||||
self.app.report_usage("geometry_on_generatecnc_button")
|
||||
self.read_form()
|
||||
|
||||
self.sel_tools = {}
|
||||
|
||||
try:
|
||||
if self.special_group:
|
||||
self.app.inform.emit("[WARNING_NOTCL]This Geometry can't be processed because it is %s geometry." %
|
||||
str(self.special_group))
|
||||
return
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# test to see if we have tools available in the tool table
|
||||
if self.ui.geo_tools_table.selectedItems():
|
||||
for x in self.ui.geo_tools_table.selectedItems():
|
||||
|
@ -3565,8 +3678,19 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
tooluid: copy.deepcopy(tooluid_value)
|
||||
})
|
||||
self.mtool_gen_cncjob()
|
||||
|
||||
self.ui.geo_tools_table.clearSelection()
|
||||
|
||||
elif self.ui.geo_tools_table.rowCount() == 1:
|
||||
tooluid = int(self.ui.geo_tools_table.item(0, 5).text())
|
||||
|
||||
for tooluid_key, tooluid_value in self.tools.items():
|
||||
if int(tooluid_key) == tooluid:
|
||||
self.sel_tools.update({
|
||||
tooluid: copy.deepcopy(tooluid_value)
|
||||
})
|
||||
self.mtool_gen_cncjob()
|
||||
self.ui.geo_tools_table.clearSelection()
|
||||
|
||||
else:
|
||||
self.app.inform.emit("[ERROR_NOTCL] Failed. No tool selected in the tool table ...")
|
||||
|
||||
|
@ -3855,6 +3979,16 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
|
|||
'[ERROR_NOTCL]Wrong value format for self.defaults["feedrate_probe"] '
|
||||
'or self.options["feedrate_probe"]')
|
||||
|
||||
# make sure that trying to make a CNCJob from an empty file is not creating an app crash
|
||||
if not self.solid_geometry:
|
||||
a = 0
|
||||
for tooluid_key in self.tools:
|
||||
if self.tools[tooluid_key]['solid_geometry'] is None:
|
||||
a += 1
|
||||
if a == len(self.tools):
|
||||
self.app.inform.emit('[ERROR_NOTCL]Cancelled. Empty file, it has no geometry...')
|
||||
return 'fail'
|
||||
|
||||
for tooluid_key in self.sel_tools:
|
||||
tool_cnt += 1
|
||||
app_obj.progress.emit(20)
|
||||
|
@ -4561,6 +4695,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||
'''
|
||||
self.exc_cnc_tools = {}
|
||||
|
||||
# flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the
|
||||
# default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects.
|
||||
self. special_group = None
|
||||
|
||||
# for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool
|
||||
# (like the one in the TCL Command), False
|
||||
|
@ -4736,6 +4873,13 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||
# set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
|
||||
self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"])
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
if self.app.defaults["global_app_level"] == 'b':
|
||||
self.ui.level.setText('<span style="color:green;"><b>Basic</b></span>')
|
||||
|
||||
else:
|
||||
self.ui.level.setText('<span style="color:red;"><b>Advanced</b></span>')
|
||||
|
||||
self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
|
||||
self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
|
||||
self.ui.modify_gcode_button.clicked.connect(self.on_modifygcode_button_click)
|
||||
|
@ -4803,13 +4947,24 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||
preamble = str(self.ui.prepend_text.get_value())
|
||||
postamble = str(self.ui.append_text.get_value())
|
||||
|
||||
self.export_gcode(filename, preamble=preamble, postamble=postamble)
|
||||
gc = self.export_gcode(filename, preamble=preamble, postamble=postamble)
|
||||
if gc == 'fail':
|
||||
return
|
||||
|
||||
self.app.file_saved.emit("gcode", filename)
|
||||
self.app.inform.emit("[success] Machine Code file saved to: %s" % filename)
|
||||
|
||||
def on_modifygcode_button_click(self, *args):
|
||||
preamble = str(self.ui.prepend_text.get_value())
|
||||
postamble = str(self.ui.append_text.get_value())
|
||||
gc = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
|
||||
if gc == 'fail':
|
||||
return
|
||||
else:
|
||||
self.app.gcode_edited = gc
|
||||
|
||||
# add the tab if it was closed
|
||||
self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "CNC Code Editor")
|
||||
self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, "Code Editor")
|
||||
|
||||
# delete the absolute and relative position and messages in the infobar
|
||||
self.app.ui.position_label.setText("")
|
||||
|
@ -4818,10 +4973,6 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||
# Switch plot_area to CNCJob tab
|
||||
self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.cncjob_tab)
|
||||
|
||||
preamble = str(self.ui.prepend_text.get_value())
|
||||
postamble = str(self.ui.append_text.get_value())
|
||||
self.app.gcode_edited = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True)
|
||||
# print(self.app.gcode_edited)
|
||||
# first clear previous text in text editor (if any)
|
||||
self.app.ui.code_editor.clear()
|
||||
|
||||
|
@ -4935,6 +5086,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
|
|||
roland = False
|
||||
hpgl = False
|
||||
|
||||
try:
|
||||
if self.special_group:
|
||||
self.app.inform.emit("[WARNING_NOTCL]This CNCJob object can't be processed because "
|
||||
"it is a %s CNCJob object." % str(self.special_group))
|
||||
return 'fail'
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# detect if using Roland postprocessor
|
||||
try:
|
||||
for key in self.cnc_tools:
|
||||
|
|
|
@ -20,6 +20,7 @@ class ABCPostProcRegister(ABCMeta):
|
|||
postprocessors[newclass.__name__] = newclass() # here is your register function
|
||||
return newclass
|
||||
|
||||
|
||||
class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
|
||||
@abstractmethod
|
||||
def start_code(self, p):
|
||||
|
@ -65,6 +66,77 @@ class FlatCAMPostProc(object, metaclass=ABCPostProcRegister):
|
|||
def spindle_stop_code(self,p):
|
||||
pass
|
||||
|
||||
|
||||
class FlatCAMPostProc_Tools(object, metaclass=ABCPostProcRegister):
|
||||
@abstractmethod
|
||||
def start_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def lift_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def down_z_start_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def lift_z_dispense_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def down_z_stop_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def toolchange_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def rapid_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def linear_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def end_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def feedrate_xy_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def feedrate_z_code(self, p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def feedrate_z_dispense_code(self,p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def spindle_fwd_code(self,p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def spindle_rev_code(self,p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def spindle_off_code(self,p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dwell_fwd_code(self,p):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dwell_rev_code(self,p):
|
||||
pass
|
||||
|
||||
|
||||
def load_postprocessors(app):
|
||||
postprocessors_path_search = [os.path.join(app.data_path,'postprocessors','*.py'),
|
||||
os.path.join('postprocessors', '*.py')]
|
||||
|
|
|
@ -490,7 +490,8 @@ class FCComboBox(QtWidgets.QComboBox):
|
|||
|
||||
|
||||
class FCInputDialog(QtWidgets.QInputDialog):
|
||||
def __init__(self, parent=None, ok=False, val=None, title=None, text=None, min=None, max=None, decimals=None):
|
||||
def __init__(self, parent=None, ok=False, val=None, title=None, text=None, min=None, max=None, decimals=None,
|
||||
init_val=None):
|
||||
super(FCInputDialog, self).__init__(parent)
|
||||
self.allow_empty = ok
|
||||
self.empty_val = val
|
||||
|
@ -498,6 +499,8 @@ class FCInputDialog(QtWidgets.QInputDialog):
|
|||
self.val = 0.0
|
||||
self.ok = ''
|
||||
|
||||
self.init_value = init_val if init_val else 0.0
|
||||
|
||||
if title is None:
|
||||
self.title = 'title'
|
||||
else:
|
||||
|
@ -521,7 +524,7 @@ class FCInputDialog(QtWidgets.QInputDialog):
|
|||
|
||||
def get_value(self):
|
||||
self.val, self.ok = self.getDouble(self, self.title, self.text, min=self.min,
|
||||
max=self.max, decimals=self.decimals)
|
||||
max=self.max, decimals=self.decimals, value=self.init_value)
|
||||
return [self.val, self.ok]
|
||||
|
||||
# "Transform", "Enter the Angle value:"
|
||||
|
|
|
@ -504,6 +504,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
sel = len(self.view.selectedIndexes()) > 0
|
||||
self.app.ui.menuprojectenable.setEnabled(sel)
|
||||
self.app.ui.menuprojectdisable.setEnabled(sel)
|
||||
self.app.ui.menuprojectviewsource.setEnabled(sel)
|
||||
|
||||
self.app.ui.menuprojectcopy.setEnabled(sel)
|
||||
self.app.ui.menuprojectedit.setEnabled(sel)
|
||||
self.app.ui.menuprojectdelete.setEnabled(sel)
|
||||
|
@ -514,14 +516,15 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
self.app.ui.menuprojectgeneratecnc.setVisible(True)
|
||||
self.app.ui.menuprojectedit.setVisible(True)
|
||||
self.app.ui.menuprojectsave.setVisible(True)
|
||||
self.app.ui.menuprojectviewsource.setVisible(True)
|
||||
|
||||
for obj in self.get_selected():
|
||||
if type(obj) != FlatCAMGeometry:
|
||||
self.app.ui.menuprojectgeneratecnc.setVisible(False)
|
||||
if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon:
|
||||
self.app.ui.menuprojectedit.setVisible(False)
|
||||
if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob:
|
||||
self.app.ui.menuprojectsave.setVisible(False)
|
||||
if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon:
|
||||
self.app.ui.menuprojectviewsource.setVisible(False)
|
||||
else:
|
||||
self.app.ui.menuprojectgeneratecnc.setVisible(False)
|
||||
|
||||
|
@ -918,7 +921,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
|
||||
except IndexError:
|
||||
FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
|
||||
|
||||
self.app.inform.emit('')
|
||||
try:
|
||||
self.app.ui.selected_scroll_area.takeWidget()
|
||||
except:
|
||||
|
|
84
ObjectUI.py
84
ObjectUI.py
|
@ -32,6 +32,19 @@ class ObjectUI(QtWidgets.QWidget):
|
|||
self.title_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
self.title_box.addWidget(self.title_label, stretch=1)
|
||||
|
||||
## App Level label
|
||||
self.level = QtWidgets.QLabel("")
|
||||
self.level.setToolTip(
|
||||
"BASIC is suitable for a beginner. Many parameters\n"
|
||||
"are hidden from the user in this mode.\n"
|
||||
"ADVANCED mode will make available all parameters.\n\n"
|
||||
"To change the application LEVEL, go to:\n"
|
||||
"Edit -> Preferences -> General and check:\n"
|
||||
"'APP. LEVEL' radio button."
|
||||
)
|
||||
self.level.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.title_box.addWidget(self.level)
|
||||
|
||||
## Box box for custom widgets
|
||||
# This gets populated in offspring implementations.
|
||||
self.custom_box = QtWidgets.QVBoxLayout()
|
||||
|
@ -237,13 +250,13 @@ class GerberObjectUI(ObjectUI):
|
|||
grid1.addWidget(self.iso_overlap_entry, 2, 1)
|
||||
|
||||
# Milling Type Radio Button
|
||||
milling_type_label = QtWidgets.QLabel('Milling Type:')
|
||||
milling_type_label.setToolTip(
|
||||
self.milling_type_label = QtWidgets.QLabel('Milling Type:')
|
||||
self.milling_type_label.setToolTip(
|
||||
"Milling type:\n"
|
||||
"- climb / best for precision milling and to reduce tool usage\n"
|
||||
"- conventional / useful when there is no backlash compensation"
|
||||
)
|
||||
grid1.addWidget(milling_type_label, 3, 0)
|
||||
grid1.addWidget(self.milling_type_label, 3, 0)
|
||||
self.milling_type_radio = RadioSet([{'label': 'Climb', 'value': 'cl'},
|
||||
{'label': 'Conv.', 'value': 'cv'}])
|
||||
grid1.addWidget(self.milling_type_radio, 3, 1)
|
||||
|
@ -256,13 +269,12 @@ class GerberObjectUI(ObjectUI):
|
|||
grid1.addWidget(self.combine_passes_cb, 4, 0)
|
||||
|
||||
# generate follow
|
||||
self.follow_cb = FCCheckBox(label='"Follow" Geo')
|
||||
self.follow_cb = FCCheckBox(label='"Follow"')
|
||||
self.follow_cb.setToolTip(
|
||||
"Generate a 'Follow' geometry.\n"
|
||||
"This means that it will cut through\n"
|
||||
"the middle of the trace.\n"
|
||||
"Requires that the Gerber file to be\n"
|
||||
"loaded with 'follow' parameter."
|
||||
"the middle of the trace."
|
||||
|
||||
)
|
||||
grid1.addWidget(self.follow_cb, 4, 1)
|
||||
|
||||
|
@ -380,16 +392,19 @@ class GerberObjectUI(ObjectUI):
|
|||
# Rounded corners
|
||||
self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
|
||||
self.noncopper_rounded_cb.setToolTip(
|
||||
"Creates a Geometry objects with polygons\n"
|
||||
"covering the copper-free areas of the PCB."
|
||||
"Resulting geometry will have rounded corners."
|
||||
)
|
||||
grid4.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
|
||||
grid4.addWidget(self.noncopper_rounded_cb, 1, 0)
|
||||
|
||||
self.generate_noncopper_button = QtWidgets.QPushButton('Generate Geometry')
|
||||
self.custom_box.addWidget(self.generate_noncopper_button)
|
||||
self.generate_noncopper_button = QtWidgets.QPushButton('Generate Geo')
|
||||
grid4.addWidget(self.generate_noncopper_button, 1, 1)
|
||||
|
||||
## Bounding box
|
||||
self.boundingbox_label = QtWidgets.QLabel('<b>Bounding Box:</b>')
|
||||
self.boundingbox_label.setToolTip(
|
||||
"Create a geometry surrounding the Gerber object.\n"
|
||||
"Square shape."
|
||||
)
|
||||
self.custom_box.addWidget(self.boundingbox_label)
|
||||
|
||||
grid5 = QtWidgets.QGridLayout()
|
||||
|
@ -411,13 +426,13 @@ class GerberObjectUI(ObjectUI):
|
|||
"their radius is equal to\n"
|
||||
"the margin."
|
||||
)
|
||||
grid5.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
|
||||
grid5.addWidget(self.bbrounded_cb, 1, 0)
|
||||
|
||||
self.generate_bb_button = QtWidgets.QPushButton('Generate Geometry')
|
||||
self.generate_bb_button = QtWidgets.QPushButton('Generate Geo')
|
||||
self.generate_bb_button.setToolTip(
|
||||
"Genrate the Geometry object."
|
||||
"Generate the Geometry object."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_bb_button)
|
||||
grid5.addWidget(self.generate_bb_button, 1, 1)
|
||||
|
||||
|
||||
class ExcellonObjectUI(ObjectUI):
|
||||
|
@ -485,7 +500,7 @@ class ExcellonObjectUI(ObjectUI):
|
|||
self.tools_box.addWidget(self.tools_table)
|
||||
|
||||
self.tools_table.setColumnCount(6)
|
||||
self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', 'Drills', 'Slots', 'Offset', 'P'])
|
||||
self.tools_table.setHorizontalHeaderLabels(['#', 'Diameter', 'Drills', 'Slots', 'Offset Z', 'P'])
|
||||
self.tools_table.setSortingEnabled(False)
|
||||
|
||||
self.tools_table.horizontalHeaderItem(0).setToolTip(
|
||||
|
@ -562,22 +577,22 @@ class ExcellonObjectUI(ObjectUI):
|
|||
self.ois_tcz_e = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
|
||||
|
||||
# Start move Z:
|
||||
startzlabel = QtWidgets.QLabel("Start move Z:")
|
||||
startzlabel.setToolTip(
|
||||
self.estartz_label = QtWidgets.QLabel("Start move Z:")
|
||||
self.estartz_label.setToolTip(
|
||||
"Tool height just before starting the work.\n"
|
||||
"Delete the value if you don't need this feature."
|
||||
)
|
||||
grid1.addWidget(startzlabel, 4, 0)
|
||||
grid1.addWidget(self.estartz_label, 4, 0)
|
||||
self.estartz_entry = FloatEntry()
|
||||
grid1.addWidget(self.estartz_entry, 4, 1)
|
||||
|
||||
# End move Z:
|
||||
endzlabel = QtWidgets.QLabel("End move Z:")
|
||||
endzlabel.setToolTip(
|
||||
self.eendz_label = QtWidgets.QLabel("End move Z:")
|
||||
self.eendz_label.setToolTip(
|
||||
"Z-axis position (height) for\n"
|
||||
"the last move."
|
||||
)
|
||||
grid1.addWidget(endzlabel, 5, 0)
|
||||
grid1.addWidget(self.eendz_label, 5, 0)
|
||||
self.eendz_entry = LengthEntry()
|
||||
grid1.addWidget(self.eendz_entry, 5, 1)
|
||||
|
||||
|
@ -593,13 +608,13 @@ class ExcellonObjectUI(ObjectUI):
|
|||
grid1.addWidget(self.feedrate_entry, 6, 1)
|
||||
|
||||
# Excellon Rapid Feedrate
|
||||
fr_rapid_label = QtWidgets.QLabel('Feedrate Rapids:')
|
||||
fr_rapid_label.setToolTip(
|
||||
self.feedrate_rapid_label = QtWidgets.QLabel('Feedrate Rapids:')
|
||||
self.feedrate_rapid_label.setToolTip(
|
||||
"Tool speed while drilling\n"
|
||||
"(in units per minute).\n"
|
||||
"This is for the rapid move G00."
|
||||
)
|
||||
grid1.addWidget(fr_rapid_label, 7, 0)
|
||||
grid1.addWidget(self.feedrate_rapid_label, 7, 0)
|
||||
self.feedrate_rapid_entry = LengthEntry()
|
||||
grid1.addWidget(self.feedrate_rapid_entry, 7, 1)
|
||||
|
||||
|
@ -976,7 +991,6 @@ class GeometryObjectUI(ObjectUI):
|
|||
)
|
||||
self.grid3.addWidget(self.mpass_cb, 4, 0)
|
||||
|
||||
|
||||
self.maxdepth_entry = LengthEntry()
|
||||
self.maxdepth_entry.setToolTip(
|
||||
"Depth of each pass (positive)."
|
||||
|
@ -1026,12 +1040,12 @@ class GeometryObjectUI(ObjectUI):
|
|||
# self.grid3.addWidget(self.gstartz_entry, 8, 1)
|
||||
|
||||
# The Z value for the end move
|
||||
endzlabel = QtWidgets.QLabel('End move Z:')
|
||||
endzlabel.setToolTip(
|
||||
self.endzlabel = QtWidgets.QLabel('End move Z:')
|
||||
self.endzlabel.setToolTip(
|
||||
"This is the height (Z) at which the CNC\n"
|
||||
"will go as the last move."
|
||||
)
|
||||
self.grid3.addWidget(endzlabel, 9, 0)
|
||||
self.grid3.addWidget(self.endzlabel, 9, 0)
|
||||
self.gendz_entry = LengthEntry()
|
||||
self.grid3.addWidget(self.gendz_entry, 9, 1)
|
||||
|
||||
|
@ -1056,13 +1070,13 @@ class GeometryObjectUI(ObjectUI):
|
|||
self.grid3.addWidget(self.cncplunge_entry, 11, 1)
|
||||
|
||||
# Feedrate rapids
|
||||
fr_rapidlabel = QtWidgets.QLabel('Feed Rate Rapids:')
|
||||
fr_rapidlabel.setToolTip(
|
||||
self.fr_rapidlabel = QtWidgets.QLabel('Feed Rate Rapids:')
|
||||
self.fr_rapidlabel.setToolTip(
|
||||
"Cutting speed in the XY\n"
|
||||
"plane in units per minute\n"
|
||||
"for the rapid movements"
|
||||
)
|
||||
self.grid3.addWidget(fr_rapidlabel, 12, 0)
|
||||
self.grid3.addWidget(self.fr_rapidlabel, 12, 0)
|
||||
self.cncfeedrate_rapid_entry = LengthEntry()
|
||||
self.grid3.addWidget(self.cncfeedrate_rapid_entry, 12, 1)
|
||||
|
||||
|
@ -1338,9 +1352,9 @@ class CNCObjectUI(ObjectUI):
|
|||
self.custom_box.addLayout(h_lay)
|
||||
|
||||
# Edit GCode Button
|
||||
self.modify_gcode_button = QtWidgets.QPushButton('Edit CNC Code')
|
||||
self.modify_gcode_button = QtWidgets.QPushButton('View CNC Code')
|
||||
self.modify_gcode_button.setToolTip(
|
||||
"Opens TAB to modify/print G-Code\n"
|
||||
"Opens TAB to view/modify/print G-Code\n"
|
||||
"file."
|
||||
)
|
||||
|
||||
|
|
|
@ -198,6 +198,13 @@ class PlotCanvas(QtCore.QObject):
|
|||
except TypeError:
|
||||
pass
|
||||
|
||||
# adjust the view camera to be slightly bigger than the bounds so the shape colleaction can be seen clearly
|
||||
# otherwise the shape collection boundary will have no border
|
||||
rect.left *= 0.96
|
||||
rect.bottom *= 0.96
|
||||
rect.right *= 1.01
|
||||
rect.top *= 1.01
|
||||
|
||||
self.vispy_canvas.view.camera.rect = rect
|
||||
|
||||
self.shape_collection.unlock_updates()
|
||||
|
|
68
README.md
68
README.md
|
@ -9,6 +9,74 @@ CAD program, and create G-Code for Isolation routing.
|
|||
|
||||
=================================================
|
||||
|
||||
21.02.2019
|
||||
|
||||
- added protection against creating CNCJob from an empty Geometry object (with no geometry inside)
|
||||
- changed the shortcut key for YouTube channel from F2 to key F4
|
||||
- changed the way APP LEVEL is showed both in Edit -> Preferences -> General tab and in each Selected Tab. Changed the ToolTips content for this.
|
||||
- added the functions for GCode View and GCode Save in Tool SolderPaste
|
||||
- some work in the Gcode generation function in Tool SolderPaste
|
||||
- added protection against trying to create a CNCJob from a solder_paste dispenser geometry. This one is different than the default Geometry and can be handled only by SolderPaste Tool.
|
||||
- ToolSolderPaste tools (nozzles) now have each it's own settings
|
||||
- creating the camlib functions for the ToolSolderPaste gcode generation functions
|
||||
- finished work in ToolSolderPaste
|
||||
|
||||
20.02.2019
|
||||
|
||||
- finished added a Tool Table for Tool SolderPaste
|
||||
- working on multi tool solder paste dispensing
|
||||
- finished the Edit -> Preferences defaults section
|
||||
- finished the UI, created the postprocessor file template
|
||||
- finished the multi-tool solder paste dispensing: it will start using the biggest nozzle, fill the pads it can, and then go to the next smaller nozzle until there are no pads without solder.
|
||||
|
||||
19.02.2019
|
||||
|
||||
- added the ability to compress the FlatCAM project on save with LZMA compression. There is a setting in Edit -> Preferences -> Compression Level between 0 and 9. 9 level yields best compression at the price of RAM usage and time spent.
|
||||
- made FlatCAM able to load old type (uncompressed) FlatCAM projects
|
||||
- fixed issue with not loading old projects that do not have certain information's required by the new versions of FlatCAM
|
||||
- compacted a bit more the GUI for Gerber Object
|
||||
- removed the Open Gerber with 'follow' menu entry and also the open_gerber Tcl Command attribute 'follow'. This is no longer required because now the follow_geometry is stored by default in a Gerber object attribute gerber_obj.follow_geometry
|
||||
- added a new parameter for the Tcl CommandIsolate, named: 'follow'. When follow = 1 (True) the resulting geometry will follow the Gerber paths.
|
||||
- added a new setting in Edit -> Preferences -> General that allow to select the type of saving for the FlatCAM project: either compressed or uncompressed. Compression introduce an time overhead to the saving/restoring of a FlatCAM project.
|
||||
- started to work on Solder Paste Dispensing Tool
|
||||
- fixed a bug in rotate from shortcut function
|
||||
- finished generating the solder paste dispense geometry
|
||||
|
||||
18.02.2019
|
||||
|
||||
- added protections again wrong values for the Buffer and Paint Tool in Geometry Editor
|
||||
- the Paint Tool in Geometry Editor will load the default values from Tool Paint in Preferences
|
||||
- when the Tools in Geometry Editor are activated, the notebook with the Tool Tab will be unhidden. After execution the notebook will hide again for the Buffer Tool.
|
||||
- changed the font in Tool names
|
||||
- added in Geometry Editor a new Tool: Transformation Tool.
|
||||
- in Geometry Editor by selecting a shape with a selection shape, that object was added multiple times (one per each selection) to the selected list, which is not intended. Bug fixed.
|
||||
- finished adding Transform Tool in Geometry Editor - everything is working as intended
|
||||
- fixed a bug in Tool Transform that made the user to not be able to capture the click coordinates with SHIFT + LMB click combo
|
||||
- added the ability to choose an App QStyle out of the offered choices (different for each OS) to be applied at the next app start (Preferences -> General -> Gui Pref -> Style Combobox)
|
||||
- added support for FlatCAM usage with High DPI monitors (4k). It is applied on the next app startup after change in Preferences -> General -> Gui Settings -> HDPI Support Checkbox
|
||||
- made the app not remember the window size if the app is maximized and remember in QSettings if it was maximized. This way we can restore the maximized state but restore the windows size unmaximized
|
||||
- added a button to clear the GUI preferences in Preferences -> General -> Gui Settings -> Clear GUI Settings
|
||||
- added key shortcuts for the shape transformations within Geometry Editor: X, Y keys for Flip(mirror), SHIFT+X, SHIFT+Y combo keys for Skew and ALT+X, ALT+Y combo keys for Offset
|
||||
- adjusted the plotcanvas.zomm_fit() function so the objects are better fit into view (with a border around)
|
||||
- modified the GUI in Objects Selected Tab to accommodate 2 different modes: basic and Advanced. In Basic mode, some of the functionality's are hidden from the user.
|
||||
- added Tool Transform preferences in Edit -> Preferences and used them through out the app
|
||||
- made the output of Panelization Tool a choice out of Gerber and Geometry type of objects. Useful for those who want to engrave multiple copies of the same design.
|
||||
|
||||
17.02.2019
|
||||
|
||||
- changed some status bar messages
|
||||
- New feature: added the capability to view the source code of the Gerber/Excellon file that was loaded into the app. The file is also stored as an object attribute for later use. The view option is in the project context menu and in Menu -> Options -> View Source
|
||||
- Serialized the source_file of the Objects so it is saved in the FlatCAM project and restored.
|
||||
- if there is a single tool in the tool list (Geometry , Excellon) and the user click the Generate GCode, use that tool even if it is not selected
|
||||
- fixed issue where after loading a project, if the default kind of CNCjob view is only 'cuts' the plot will revert to the 'all' type
|
||||
- in Editors, if the modifier key set in Preferences (CTRL or SHIFT key) is pressed at the end of one tool operation it will automatically continue to that action until the modifier is no longer pressed when Select tool will be automatically selected.
|
||||
- in Geometry Editor, on entry the notebook is automatically hidden and restored on Geometry Editor exit.
|
||||
- when pressing Escape in Geometry Editor it will automatically deselect any shape not only the currently selected tool.
|
||||
- when deselecting an object in Project menu the status bar selection message is deleted
|
||||
- added ability to save the Gerber file content that is stored in FlatCAM on Gerber file loading. It's useful to recover from saved FlatCAM projects when the source files are no longer available.
|
||||
- fixed an issue where the function handler that changed the layout had a parameter changed accidentally by an index value passed by the 'activate' signal to which was connected
|
||||
- fixed bug in paint function in Geometry Editor that didn't allow painting due of overlap value
|
||||
|
||||
16.02.2019
|
||||
|
||||
- added the 'Save' menu entry to the Project context menu, for CNCJob: it will export the GCode.
|
||||
|
|
383
camlib.py
383
camlib.py
|
@ -92,8 +92,11 @@ class Geometry(object):
|
|||
# Final geometry: MultiPolygon or list (of geometry constructs)
|
||||
self.solid_geometry = None
|
||||
|
||||
# Final geometry: MultiLineString or list (of LineString or Points)
|
||||
self.follow_geometry = None
|
||||
|
||||
# Attributes to be included in serialization
|
||||
self.ser_attrs = ["units", 'solid_geometry']
|
||||
self.ser_attrs = ["units", 'solid_geometry', 'follow_geometry']
|
||||
|
||||
# Flattened geometry (list of paths only)
|
||||
self.flat_geometry = []
|
||||
|
@ -500,7 +503,7 @@ class Geometry(object):
|
|||
#
|
||||
# return self.flat_geometry, self.flat_geometry_rtree
|
||||
|
||||
def isolation_geometry(self, offset, iso_type=2, corner=None):
|
||||
def isolation_geometry(self, offset, iso_type=2, corner=None, follow=None):
|
||||
"""
|
||||
Creates contours around geometry at a given
|
||||
offset distance.
|
||||
|
@ -542,16 +545,24 @@ class Geometry(object):
|
|||
# the previously commented block is replaced with this block - regression - to solve the bug with multiple
|
||||
# isolation passes cutting from the copper features
|
||||
if offset == 0:
|
||||
geo_iso = self.solid_geometry
|
||||
else:
|
||||
if corner is None:
|
||||
geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4))
|
||||
if follow:
|
||||
geo_iso = self.follow_geometry
|
||||
else:
|
||||
geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner)
|
||||
geo_iso = self.solid_geometry
|
||||
else:
|
||||
if follow:
|
||||
geo_iso = self.follow_geometry
|
||||
else:
|
||||
if corner is None:
|
||||
geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4))
|
||||
else:
|
||||
geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4),
|
||||
join_style=corner)
|
||||
|
||||
# end of replaced block
|
||||
|
||||
if iso_type == 2:
|
||||
if follow:
|
||||
return geo_iso
|
||||
elif iso_type == 2:
|
||||
return geo_iso
|
||||
elif iso_type == 0:
|
||||
return self.get_exteriors(geo_iso)
|
||||
|
@ -1375,8 +1386,6 @@ class Geometry(object):
|
|||
except AttributeError:
|
||||
self.app.inform.emit("[ERROR_NOTCL] Failed to mirror. No object selected")
|
||||
|
||||
|
||||
|
||||
def rotate(self, angle, point):
|
||||
"""
|
||||
Rotate an object by an angle (in degrees) around the provided coordinates.
|
||||
|
@ -1891,8 +1900,12 @@ class Gerber (Geometry):
|
|||
# Initialize parent
|
||||
Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
|
||||
|
||||
# will store the Gerber geometry's as solids
|
||||
self.solid_geometry = Polygon()
|
||||
|
||||
# will store the Gerber geometry's as paths
|
||||
self.follow_geometry = []
|
||||
|
||||
# Number format
|
||||
self.int_digits = 3
|
||||
"""Number of integer digits in Gerber numbers. Used during parsing."""
|
||||
|
@ -1913,11 +1926,13 @@ class Gerber (Geometry):
|
|||
# Aperture Macros
|
||||
self.aperture_macros = {}
|
||||
|
||||
self.source_file = ''
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from Geometry.
|
||||
self.ser_attrs += ['int_digits', 'frac_digits', 'apertures',
|
||||
'aperture_macros', 'solid_geometry']
|
||||
'aperture_macros', 'solid_geometry', 'source_file']
|
||||
|
||||
#### Parser patterns ####
|
||||
# FS - Format Specification
|
||||
|
@ -2113,10 +2128,10 @@ class Gerber (Geometry):
|
|||
yield line
|
||||
break
|
||||
|
||||
self.parse_lines(line_generator(), follow=follow)
|
||||
self.parse_lines(line_generator())
|
||||
|
||||
#@profile
|
||||
def parse_lines(self, glines, follow=False):
|
||||
def parse_lines(self, glines):
|
||||
"""
|
||||
Main Gerber parser. Reads Gerber and populates ``self.paths``, ``self.apertures``,
|
||||
``self.flashes``, ``self.regions`` and ``self.units``.
|
||||
|
@ -2143,6 +2158,9 @@ class Gerber (Geometry):
|
|||
# applying a union for every new polygon.
|
||||
poly_buffer = []
|
||||
|
||||
# store here the follow geometry
|
||||
follow_buffer = []
|
||||
|
||||
last_path_aperture = None
|
||||
current_aperture = None
|
||||
|
||||
|
@ -2185,6 +2203,8 @@ class Gerber (Geometry):
|
|||
for gline in glines:
|
||||
line_num += 1
|
||||
|
||||
self.source_file += gline + '\n'
|
||||
|
||||
### Cleanup
|
||||
gline = gline.strip(' \r\n')
|
||||
# log.debug("Line=%3s %s" % (line_num, gline))
|
||||
|
@ -2206,10 +2226,11 @@ class Gerber (Geometry):
|
|||
# --- Buffered ----
|
||||
width = self.apertures[last_path_aperture]["size"]
|
||||
|
||||
if follow:
|
||||
geo = LineString(path)
|
||||
else:
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
geo = LineString(path)
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
if not geo.is_empty:
|
||||
poly_buffer.append(geo)
|
||||
|
||||
|
@ -2220,9 +2241,14 @@ class Gerber (Geometry):
|
|||
# TODO: Remove when bug fixed
|
||||
if len(poly_buffer) > 0:
|
||||
if current_polarity == 'D':
|
||||
self.follow_geometry = self.solid_geometry.union(cascaded_union(follow_buffer))
|
||||
self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
|
||||
|
||||
else:
|
||||
self.solid_geometry = self.solid_geometry.difference(cascaded_union(poly_buffer))
|
||||
self.follow_geometry = self.solid_geometry.difference(cascaded_union(follow_buffer))
|
||||
self.solid_geometry = self.solid_geometry.union(cascaded_union(poly_buffer))
|
||||
|
||||
follow_buffer = []
|
||||
poly_buffer = []
|
||||
|
||||
current_polarity = match.group(1)
|
||||
|
@ -2366,8 +2392,6 @@ class Gerber (Geometry):
|
|||
log.debug("Bare op-code %d." % current_operation_code)
|
||||
# flash = Gerber.create_flash_geometry(Point(path[-1]),
|
||||
# self.apertures[current_aperture])
|
||||
if follow:
|
||||
continue
|
||||
flash = Gerber.create_flash_geometry(
|
||||
Point(current_x, current_y), self.apertures[current_aperture],
|
||||
int(self.steps_per_circle))
|
||||
|
@ -2403,10 +2427,11 @@ class Gerber (Geometry):
|
|||
# --- Buffered ----
|
||||
width = self.apertures[last_path_aperture]["size"]
|
||||
|
||||
if follow:
|
||||
geo = LineString(path)
|
||||
else:
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
geo = LineString(path)
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
if not geo.is_empty:
|
||||
poly_buffer.append(geo)
|
||||
|
||||
|
@ -2422,10 +2447,11 @@ class Gerber (Geometry):
|
|||
## --- Buffered ---
|
||||
width = self.apertures[last_path_aperture]["size"]
|
||||
|
||||
if follow:
|
||||
geo = LineString(path)
|
||||
else:
|
||||
geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
|
||||
geo = LineString(path)
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
geo = LineString(path).buffer(width/1.999, int(self.steps_per_circle / 4))
|
||||
if not geo.is_empty:
|
||||
poly_buffer.append(geo)
|
||||
|
||||
|
@ -2443,6 +2469,7 @@ class Gerber (Geometry):
|
|||
if current_operation_code == 2:
|
||||
if geo:
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
poly_buffer.append(geo)
|
||||
continue
|
||||
|
||||
|
@ -2462,15 +2489,14 @@ class Gerber (Geometry):
|
|||
# "aperture": last_path_aperture})
|
||||
|
||||
# --- Buffered ---
|
||||
if follow:
|
||||
region = Polygon()
|
||||
else:
|
||||
region = Polygon(path)
|
||||
|
||||
region = Polygon()
|
||||
if not region.is_empty:
|
||||
follow_buffer.append(region)
|
||||
|
||||
region = Polygon(path)
|
||||
if not region.is_valid:
|
||||
if not follow:
|
||||
region = region.buffer(0, int(self.steps_per_circle / 4))
|
||||
|
||||
region = region.buffer(0, int(self.steps_per_circle / 4))
|
||||
if not region.is_empty:
|
||||
poly_buffer.append(region)
|
||||
|
||||
|
@ -2527,7 +2553,7 @@ class Gerber (Geometry):
|
|||
if path[-1] != [linear_x, linear_y]:
|
||||
path.append([linear_x, linear_y])
|
||||
|
||||
if follow == 0 and making_region is False:
|
||||
if making_region is False:
|
||||
# if the aperture is rectangle then add a rectangular shape having as parameters the
|
||||
# coordinates of the start and end point and also the width and height
|
||||
# of the 'R' aperture
|
||||
|
@ -2553,29 +2579,35 @@ class Gerber (Geometry):
|
|||
geo = None
|
||||
|
||||
## --- BUFFERED ---
|
||||
# this treats the case when we are storing geometry as paths only
|
||||
if making_region:
|
||||
if follow:
|
||||
geo = Polygon()
|
||||
else:
|
||||
elem = [linear_x, linear_y]
|
||||
if elem != path[-1]:
|
||||
path.append([linear_x, linear_y])
|
||||
try:
|
||||
geo = Polygon(path)
|
||||
except ValueError:
|
||||
log.warning("Problem %s %s" % (gline, line_num))
|
||||
self.app.inform.emit("[ERROR] Region does not have enough points. "
|
||||
"File will be processed but there are parser errors. "
|
||||
"Line number: %s" % str(line_num))
|
||||
geo = Polygon()
|
||||
else:
|
||||
geo = LineString(path)
|
||||
try:
|
||||
if self.apertures[last_path_aperture]["type"] != 'R':
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
except:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
if making_region:
|
||||
elem = [linear_x, linear_y]
|
||||
if elem != path[-1]:
|
||||
path.append([linear_x, linear_y])
|
||||
try:
|
||||
geo = Polygon(path)
|
||||
except ValueError:
|
||||
log.warning("Problem %s %s" % (gline, line_num))
|
||||
self.app.inform.emit("[ERROR] Region does not have enough points. "
|
||||
"File will be processed but there are parser errors. "
|
||||
"Line number: %s" % str(line_num))
|
||||
else:
|
||||
if last_path_aperture is None:
|
||||
log.warning("No aperture defined for curent path. (%d)" % line_num)
|
||||
width = self.apertures[last_path_aperture]["size"] # TODO: WARNING this should fail!
|
||||
#log.debug("Line %d: Setting aperture to %s before buffering." % (line_num, last_path_aperture))
|
||||
if follow:
|
||||
geo = LineString(path)
|
||||
else:
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
|
||||
try:
|
||||
if self.apertures[last_path_aperture]["type"] != 'R':
|
||||
|
@ -2598,19 +2630,23 @@ class Gerber (Geometry):
|
|||
# Create path draw so far.
|
||||
if len(path) > 1:
|
||||
# --- Buffered ----
|
||||
|
||||
# this treats the case when we are storing geometry as paths
|
||||
geo = LineString(path)
|
||||
if not geo.is_empty:
|
||||
try:
|
||||
if self.apertures[current_aperture]["type"] != 'R':
|
||||
follow_buffer.append(geo)
|
||||
except:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
width = self.apertures[last_path_aperture]["size"]
|
||||
|
||||
if follow:
|
||||
geo = LineString(path)
|
||||
else:
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
if not geo.is_empty:
|
||||
try:
|
||||
if self.apertures[current_aperture]["type"] != 'R':
|
||||
poly_buffer.append(geo)
|
||||
else:
|
||||
pass
|
||||
except:
|
||||
poly_buffer.append(geo)
|
||||
|
||||
|
@ -2619,11 +2655,12 @@ class Gerber (Geometry):
|
|||
|
||||
# --- BUFFERED ---
|
||||
# Draw the flash
|
||||
if follow:
|
||||
continue
|
||||
# this treats the case when we are storing geometry as paths
|
||||
follow_buffer.append(Point([linear_x, linear_y]))
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
flash = Gerber.create_flash_geometry(
|
||||
Point(
|
||||
[linear_x, linear_y]),
|
||||
Point( [linear_x, linear_y]),
|
||||
self.apertures[current_aperture],
|
||||
int(self.steps_per_circle)
|
||||
)
|
||||
|
@ -2709,10 +2746,13 @@ class Gerber (Geometry):
|
|||
# --- BUFFERED ---
|
||||
width = self.apertures[last_path_aperture]["size"]
|
||||
|
||||
if follow:
|
||||
buffered = LineString(path)
|
||||
else:
|
||||
buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
|
||||
# this treats the case when we are storing geometry as paths
|
||||
geo = LineString(path)
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
buffered = LineString(path).buffer(width / 1.999, int(self.steps_per_circle))
|
||||
if not buffered.is_empty:
|
||||
poly_buffer.append(buffered)
|
||||
|
||||
|
@ -2831,19 +2871,24 @@ class Gerber (Geometry):
|
|||
else:
|
||||
# EOF, create shapely LineString if something still in path
|
||||
## --- Buffered ---
|
||||
|
||||
# this treats the case when we are storing geometry as paths
|
||||
geo = LineString(path)
|
||||
if not geo.is_empty:
|
||||
follow_buffer.append(geo)
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
width = self.apertures[last_path_aperture]["size"]
|
||||
if follow:
|
||||
geo = LineString(path)
|
||||
else:
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
geo = LineString(path).buffer(width / 1.999, int(self.steps_per_circle / 4))
|
||||
if not geo.is_empty:
|
||||
poly_buffer.append(geo)
|
||||
|
||||
# --- Apply buffer ---
|
||||
if follow:
|
||||
self.solid_geometry = poly_buffer
|
||||
return
|
||||
|
||||
# this treats the case when we are storing geometry as paths
|
||||
self.follow_geometry = follow_buffer
|
||||
|
||||
# this treats the case when we are storing geometry as solids
|
||||
log.warning("Joining %d polygons." % len(poly_buffer))
|
||||
|
||||
if len(poly_buffer) == 0:
|
||||
|
@ -3293,6 +3338,8 @@ class Excellon(Geometry):
|
|||
# self.slots (list) to store the slots; each is a dictionary
|
||||
self.slots = []
|
||||
|
||||
self.source_file = ''
|
||||
|
||||
# it serve to flag if a start routing or a stop routing was encountered
|
||||
# if a stop is encounter and this flag is still 0 (so there is no stop for a previous start) issue error
|
||||
self.routing_flag = 1
|
||||
|
@ -3323,7 +3370,8 @@ class Excellon(Geometry):
|
|||
# Always append to it because it carries contents
|
||||
# from Geometry.
|
||||
self.ser_attrs += ['tools', 'drills', 'zeros', 'excellon_format_upper_mm', 'excellon_format_lower_mm',
|
||||
'excellon_format_upper_in', 'excellon_format_lower_in', 'excellon_units', 'slots']
|
||||
'excellon_format_upper_in', 'excellon_format_lower_in', 'excellon_units', 'slots',
|
||||
'source_file']
|
||||
|
||||
#### Patterns ####
|
||||
# Regex basics:
|
||||
|
@ -3469,6 +3517,8 @@ class Excellon(Geometry):
|
|||
line_num += 1
|
||||
# log.debug("%3d %s" % (line_num, str(eline)))
|
||||
|
||||
self.source_file += eline
|
||||
|
||||
# Cleanup lines
|
||||
eline = eline.strip(' \r\n')
|
||||
|
||||
|
@ -3819,7 +3869,7 @@ class Excellon(Geometry):
|
|||
self.drills.append({'point': Point((coordx, coordy)), 'tool': current_tool})
|
||||
repeat -= 1
|
||||
repeating_x = repeating_y = 0
|
||||
log.debug("{:15} {:8} {:8}".format(eline, x, y))
|
||||
# log.debug("{:15} {:8} {:8}".format(eline, x, y))
|
||||
continue
|
||||
|
||||
## Coordinates with period: Use literally. ##
|
||||
|
@ -3901,7 +3951,7 @@ class Excellon(Geometry):
|
|||
self.drills.append({'point': Point((coordx, coordy)), 'tool': current_tool})
|
||||
repeat -= 1
|
||||
repeating_x = repeating_y = 0
|
||||
log.debug("{:15} {:8} {:8}".format(eline, x, y))
|
||||
# log.debug("{:15} {:8} {:8}".format(eline, x, y))
|
||||
continue
|
||||
|
||||
#### Header ####
|
||||
|
@ -4004,7 +4054,6 @@ class Excellon(Geometry):
|
|||
# is finished since the tools definitions are spread in the Excellon body. We use as units the value
|
||||
# from self.defaults['excellon_units']
|
||||
log.info("Zeros: %s, Units %s." % (self.zeros, self.units))
|
||||
|
||||
except Exception as e:
|
||||
log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
|
||||
msg = "[ERROR_NOTCL] An internal error has ocurred. See shell.\n"
|
||||
|
@ -4454,6 +4503,9 @@ class CNCjob(Geometry):
|
|||
self.pp_excellon_name = pp_excellon_name
|
||||
self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
|
||||
|
||||
self.pp_solderpaste_name = None
|
||||
|
||||
|
||||
# Controls if the move from Z_Toolchange to Z_Move is done fast with G0 or normally with G1
|
||||
self.f_plunge = None
|
||||
|
||||
|
@ -4478,6 +4530,8 @@ class CNCjob(Geometry):
|
|||
self.oldx = None
|
||||
self.oldy = None
|
||||
|
||||
self.tool = 0.0
|
||||
|
||||
# Attributes to be included in serialization
|
||||
# Always append to it because it carries contents
|
||||
# from Geometry.
|
||||
|
@ -4977,7 +5031,7 @@ class CNCjob(Geometry):
|
|||
:param depthpercut: Maximum depth in each pass.
|
||||
:param extracut: Adds (or not) an extra cut at the end of each path
|
||||
overlapping the first point in path to ensure complete copper removal
|
||||
:return: None
|
||||
:return: GCode - string
|
||||
"""
|
||||
|
||||
log.debug("Generate_from_multitool_geometry()")
|
||||
|
@ -5103,7 +5157,9 @@ class CNCjob(Geometry):
|
|||
log.debug("Starting G-Code...")
|
||||
path_count = 0
|
||||
current_pt = (0, 0)
|
||||
|
||||
pt, geo = storage.nearest(current_pt)
|
||||
|
||||
try:
|
||||
while True:
|
||||
path_count += 1
|
||||
|
@ -5392,13 +5448,162 @@ class CNCjob(Geometry):
|
|||
|
||||
return self.gcode
|
||||
|
||||
def generate_gcode_from_solderpaste_geo(self, **kwargs):
|
||||
"""
|
||||
Algorithm to generate from multitool Geometry.
|
||||
|
||||
Algorithm description:
|
||||
----------------------
|
||||
Uses RTree to find the nearest path to follow.
|
||||
|
||||
:return: Gcode string
|
||||
"""
|
||||
|
||||
log.debug("Generate_from_solderpaste_geometry()")
|
||||
|
||||
## Index first and last points in paths
|
||||
# What points to index.
|
||||
def get_pts(o):
|
||||
return [o.coords[0], o.coords[-1]]
|
||||
|
||||
self.gcode = ""
|
||||
|
||||
if not kwargs:
|
||||
log.debug("camlib.generate_from_solderpaste_geo() --> No tool in the solderpaste geometry.")
|
||||
self.app.inform.emit("[ERROR_NOTCL] There is no tool data in the SolderPaste geometry.")
|
||||
|
||||
|
||||
# this is the tool diameter, it is used as such to accommodate the postprocessor who need the tool diameter
|
||||
# given under the name 'toolC'
|
||||
|
||||
self.postdata['z_start'] = kwargs['data']['tools_solderpaste_z_start']
|
||||
self.postdata['z_dispense'] = kwargs['data']['tools_solderpaste_z_dispense']
|
||||
self.postdata['z_stop'] = kwargs['data']['tools_solderpaste_z_stop']
|
||||
self.postdata['z_travel'] = kwargs['data']['tools_solderpaste_z_travel']
|
||||
self.postdata['z_toolchange'] = kwargs['data']['tools_solderpaste_z_toolchange']
|
||||
self.postdata['xy_toolchange'] = kwargs['data']['tools_solderpaste_xy_toolchange']
|
||||
self.postdata['frxy'] = kwargs['data']['tools_solderpaste_frxy']
|
||||
self.postdata['frz'] = kwargs['data']['tools_solderpaste_frz']
|
||||
self.postdata['frz_dispense'] = kwargs['data']['tools_solderpaste_frz_dispense']
|
||||
self.postdata['speedfwd'] = kwargs['data']['tools_solderpaste_speedfwd']
|
||||
self.postdata['dwellfwd'] = kwargs['data']['tools_solderpaste_dwellfwd']
|
||||
self.postdata['speedrev'] = kwargs['data']['tools_solderpaste_speedrev']
|
||||
self.postdata['dwellrev'] = kwargs['data']['tools_solderpaste_dwellrev']
|
||||
self.postdata['pp_solderpaste_name'] = kwargs['data']['tools_solderpaste_pp']
|
||||
|
||||
self.postdata['toolC'] = kwargs['tooldia']
|
||||
|
||||
self.pp_solderpaste_name = kwargs['data']['tools_solderpaste_pp'] if kwargs['data']['tools_solderpaste_pp'] \
|
||||
else self.app.defaults['tools_solderpaste_pp']
|
||||
p = self.app.postprocessors[self.pp_solderpaste_name]
|
||||
|
||||
## Flatten the geometry. Only linear elements (no polygons) remain.
|
||||
flat_geometry = self.flatten(kwargs['solid_geometry'], pathonly=True)
|
||||
log.debug("%d paths" % len(flat_geometry))
|
||||
|
||||
# Create the indexed storage.
|
||||
storage = FlatCAMRTreeStorage()
|
||||
storage.get_points = get_pts
|
||||
|
||||
# Store the geometry
|
||||
log.debug("Indexing geometry before generating G-Code...")
|
||||
for shape in flat_geometry:
|
||||
if shape is not None:
|
||||
storage.insert(shape)
|
||||
|
||||
# Initial G-Code
|
||||
self.gcode = self.doformat(p.start_code)
|
||||
self.gcode += self.doformat(p.spindle_off_code)
|
||||
self.gcode += self.doformat(p.toolchange_code)
|
||||
|
||||
## Iterate over geometry paths getting the nearest each time.
|
||||
log.debug("Starting SolderPaste G-Code...")
|
||||
path_count = 0
|
||||
current_pt = (0, 0)
|
||||
|
||||
pt, geo = storage.nearest(current_pt)
|
||||
|
||||
try:
|
||||
while True:
|
||||
path_count += 1
|
||||
|
||||
# Remove before modifying, otherwise deletion will fail.
|
||||
storage.remove(geo)
|
||||
|
||||
# If last point in geometry is the nearest but prefer the first one if last point == first point
|
||||
# then reverse coordinates.
|
||||
if pt != geo.coords[0] and pt == geo.coords[-1]:
|
||||
geo.coords = list(geo.coords)[::-1]
|
||||
|
||||
self.gcode += self.create_soldepaste_gcode(geo, p=p)
|
||||
current_pt = geo.coords[-1]
|
||||
pt, geo = storage.nearest(current_pt) # Next
|
||||
|
||||
except StopIteration: # Nothing found in storage.
|
||||
pass
|
||||
|
||||
log.debug("Finishing SolderPste G-Code... %s paths traced." % path_count)
|
||||
|
||||
# Finish
|
||||
self.gcode += self.doformat(p.lift_code)
|
||||
self.gcode += self.doformat(p.end_code)
|
||||
|
||||
return self.gcode
|
||||
|
||||
def create_soldepaste_gcode(self, geometry, p):
|
||||
gcode = ''
|
||||
path = geometry.coords
|
||||
|
||||
if type(geometry) == LineString or type(geometry) == LinearRing:
|
||||
# Move fast to 1st point
|
||||
gcode += self.doformat(p.rapid_code, x=path[0][0], y=path[0][1]) # Move to first point
|
||||
|
||||
# Move down to cutting depth
|
||||
gcode += self.doformat(p.feedrate_z_code)
|
||||
gcode += self.doformat(p.down_z_start_code)
|
||||
gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
|
||||
gcode += self.doformat(p.dwell_fwd_code)
|
||||
gcode += self.doformat(p.feedrate_z_dispense_code)
|
||||
gcode += self.doformat(p.lift_z_dispense_code)
|
||||
gcode += self.doformat(p.feedrate_xy_code)
|
||||
|
||||
# Cutting...
|
||||
for pt in path[1:]:
|
||||
gcode += self.doformat(p.linear_code, x=pt[0], y=pt[1]) # Linear motion to point
|
||||
|
||||
# Up to travelling height.
|
||||
gcode += self.doformat(p.spindle_off_code) # Stop dispensing
|
||||
gcode += self.doformat(p.spindle_rev_code)
|
||||
gcode += self.doformat(p.down_z_stop_code)
|
||||
gcode += self.doformat(p.spindle_off_code)
|
||||
gcode += self.doformat(p.dwell_rev_code)
|
||||
gcode += self.doformat(p.feedrate_z_code)
|
||||
gcode += self.doformat(p.lift_code)
|
||||
elif type(geometry) == Point:
|
||||
gcode += self.doformat(p.linear_code, x=path[0][0], y=path[0][1]) # Move to first point
|
||||
|
||||
gcode += self.doformat(p.feedrate_z_dispense_code)
|
||||
gcode += self.doformat(p.down_z_start_code)
|
||||
gcode += self.doformat(p.spindle_fwd_code) # Start dispensing
|
||||
gcode += self.doformat(p.dwell_fwd_code)
|
||||
gcode += self.doformat(p.lift_z_dispense_code)
|
||||
|
||||
gcode += self.doformat(p.spindle_off_code) # Stop dispensing
|
||||
gcode += self.doformat(p.spindle_rev_code)
|
||||
gcode += self.doformat(p.spindle_off_code)
|
||||
gcode += self.doformat(p.down_z_stop_code)
|
||||
gcode += self.doformat(p.dwell_rev_code)
|
||||
gcode += self.doformat(p.feedrate_z_code)
|
||||
gcode += self.doformat(p.lift_code)
|
||||
return gcode
|
||||
|
||||
def create_gcode_single_pass(self, geometry, extracut, tolerance):
|
||||
# G-code. Note: self.linear2gcode() and self.point2gcode() will lower and raise the tool every time.
|
||||
gcode_single_pass = ''
|
||||
|
||||
if type(geometry) == LineString or type(geometry) == LinearRing:
|
||||
if extracut is False:
|
||||
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance, )
|
||||
gcode_single_pass = self.linear2gcode(geometry, tolerance=tolerance)
|
||||
else:
|
||||
if geometry.is_ring:
|
||||
gcode_single_pass = self.linear2gcode_extra(geometry, tolerance=tolerance)
|
||||
|
@ -5503,7 +5708,8 @@ class CNCjob(Geometry):
|
|||
else:
|
||||
command['Z'] = 0
|
||||
|
||||
elif 'grbl_laser' in self.pp_excellon_name or 'grbl_laser' in self.pp_geometry_name:
|
||||
elif 'grbl_laser' in self.pp_excellon_name or 'grbl_laser' in self.pp_geometry_name or \
|
||||
(self.pp_solderpaste_name is not None and 'Paste' in self.pp_solderpaste_name):
|
||||
match_lsr = re.search(r"X([\+-]?\d+.[\+-]?\d+)\s*Y([\+-]?\d+.[\+-]?\d+)", gline)
|
||||
if match_lsr:
|
||||
command['X'] = float(match_lsr.group(1).replace(" ", ""))
|
||||
|
@ -5517,7 +5723,12 @@ class CNCjob(Geometry):
|
|||
command['Z'] = 1
|
||||
else:
|
||||
command['Z'] = 0
|
||||
|
||||
elif self.pp_solderpaste is not None:
|
||||
if 'Paste' in self.pp_solderpaste:
|
||||
match_paste = re.search(r"X([\+-]?\d+.[\+-]?\d+)\s*Y([\+-]?\d+.[\+-]?\d+)", gline)
|
||||
if match_paste:
|
||||
command['X'] = float(match_paste.group(1).replace(" ", ""))
|
||||
command['Y'] = float(match_paste.group(2).replace(" ", ""))
|
||||
else:
|
||||
match = re.search(r'^\s*([A-Z])\s*([\+\-\.\d\s]+)', gline)
|
||||
while match:
|
||||
|
|
|
@ -18,7 +18,14 @@ class ToolCalculator(FlatCAMTool):
|
|||
self.app = app
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
######################
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
from FlatCAMTool import FlatCAMTool
|
||||
from copy import copy,deepcopy
|
||||
from ObjectCollection import *
|
||||
from FlatCAMApp import *
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from GUIElements import IntEntry, RadioSet, LengthEntry
|
||||
|
||||
from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
|
||||
|
||||
|
||||
class ToolCutOut(FlatCAMTool):
|
||||
class CutOut(FlatCAMTool):
|
||||
|
||||
toolName = "Cutout PCB"
|
||||
|
||||
|
@ -16,7 +11,14 @@ class ToolCutOut(FlatCAMTool):
|
|||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
## Form Layout
|
||||
|
|
|
@ -15,7 +15,14 @@ class DblSidedTool(FlatCAMTool):
|
|||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
self.empty_lb = QtWidgets.QLabel("")
|
||||
|
|
|
@ -12,7 +12,14 @@ class Film(FlatCAMTool):
|
|||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
# Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
# Form Layout
|
||||
|
|
|
@ -12,7 +12,14 @@ class ToolImage(FlatCAMTool):
|
|||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
# Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>IMAGE to PCB</b></font>")
|
||||
title_label = QtWidgets.QLabel("%s" % 'Image to PCB')
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
# Form Layout
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from FlatCAMTool import FlatCAMTool
|
||||
from copy import copy,deepcopy
|
||||
# from GUIElements import IntEntry, RadioSet, FCEntry
|
||||
# from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
|
||||
from ObjectCollection import *
|
||||
import time
|
||||
|
||||
|
@ -24,7 +22,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.tools_frame.setLayout(self.tools_box)
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.tools_box.addWidget(title_label)
|
||||
|
||||
## Form Layout
|
||||
|
|
|
@ -14,7 +14,14 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
Geometry.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"])
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
self.tools_frame = QtWidgets.QFrame()
|
||||
|
|
|
@ -13,7 +13,14 @@ class Panelize(FlatCAMTool):
|
|||
self.app = app
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
## Form Layout
|
||||
|
@ -119,6 +126,18 @@ class Panelize(FlatCAMTool):
|
|||
)
|
||||
form_layout.addRow(self.rows_label, self.rows)
|
||||
|
||||
## Type of resulting Panel object
|
||||
self.panel_type_radio = RadioSet([{'label': 'Gerber', 'value': 'gerber'},
|
||||
{'label': 'Geometry', 'value': 'geometry'}])
|
||||
self.panel_type_label = QtWidgets.QLabel("Panel Type:")
|
||||
self.panel_type_label.setToolTip(
|
||||
"Choose the type of object for the panel object:\n"
|
||||
"- Geometry\n"
|
||||
"- Gerber"
|
||||
)
|
||||
form_layout.addRow(self.panel_type_label)
|
||||
form_layout.addRow(self.panel_type_radio)
|
||||
|
||||
## Constrains
|
||||
self.constrain_cb = FCCheckBox("Constrain panel within:")
|
||||
self.constrain_cb.setToolTip(
|
||||
|
@ -149,7 +168,6 @@ class Panelize(FlatCAMTool):
|
|||
self.constrain_sel = OptionalInputSection(
|
||||
self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
|
||||
|
||||
|
||||
## Buttons
|
||||
hlay_2 = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay_2)
|
||||
|
@ -225,6 +243,10 @@ class Panelize(FlatCAMTool):
|
|||
self.app.defaults["tools_panelize_constrainy"] else 0.0
|
||||
self.y_height_entry.set_value(float(y_w))
|
||||
|
||||
panel_type = self.app.defaults["tools_panelize_panel_type"] if \
|
||||
self.app.defaults["tools_panelize_panel_type"] else 'gerber'
|
||||
self.panel_type_radio.set_value(panel_type)
|
||||
|
||||
def on_type_obj_index_changed(self):
|
||||
obj_type = self.type_obj_combo.currentIndex()
|
||||
self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
|
@ -337,6 +359,9 @@ class Panelize(FlatCAMTool):
|
|||
"use a number.")
|
||||
return
|
||||
|
||||
panel_type = str(self.panel_type_radio.get_value())
|
||||
|
||||
|
||||
if 0 in {columns, rows}:
|
||||
self.app.inform.emit("[ERROR_NOTCL]Columns or Rows are zero value. Change them to a positive integer.")
|
||||
return "Columns or Rows are zero value. Change them to a positive integer."
|
||||
|
@ -541,7 +566,8 @@ class Panelize(FlatCAMTool):
|
|||
self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
|
||||
else:
|
||||
self.app.progress.emit(50)
|
||||
self.app.new_object("geometry", self.outname, job_init_geometry, plot=True, autoselected=True)
|
||||
self.app.new_object(panel_type, self.outname, job_init_geometry,
|
||||
plot=True, autoselected=True)
|
||||
|
||||
if self.constrain_flag is False:
|
||||
self.app.inform.emit("[success]Panel done...")
|
||||
|
|
|
@ -22,7 +22,14 @@ class Properties(FlatCAMTool):
|
|||
self.properties_frame.setLayout(self.properties_box)
|
||||
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b> %s</b></font>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.properties_box.addWidget(title_label)
|
||||
|
||||
# self.layout.setMargin(0) # PyQt4
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,7 +20,14 @@ class ToolTransform(FlatCAMTool):
|
|||
self.transform_lay = QtWidgets.QVBoxLayout()
|
||||
self.layout.addLayout(self.transform_lay)
|
||||
## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.transform_lay.addWidget(title_label)
|
||||
|
||||
self.empty_label = QtWidgets.QLabel("")
|
||||
|
@ -368,18 +375,64 @@ class ToolTransform(FlatCAMTool):
|
|||
self.app.ui.notebook.setTabText(2, "Transform Tool")
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+R', **kwargs)
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+T', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
## Initialize form
|
||||
self.rotate_entry.set_value('0')
|
||||
self.skewx_entry.set_value('0')
|
||||
self.skewy_entry.set_value('0')
|
||||
self.scalex_entry.set_value('1')
|
||||
self.scaley_entry.set_value('1')
|
||||
self.offx_entry.set_value('0')
|
||||
self.offy_entry.set_value('0')
|
||||
self.flip_ref_cb.setChecked(False)
|
||||
if self.app.defaults["tools_transform_rotate"]:
|
||||
self.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"])
|
||||
else:
|
||||
self.rotate_entry.set_value(0.0)
|
||||
|
||||
if self.app.defaults["tools_transform_skew_x"]:
|
||||
self.skewx_entry.set_value(self.app.defaults["tools_transform_skew_x"])
|
||||
else:
|
||||
self.skewx_entry.set_value(0.0)
|
||||
|
||||
if self.app.defaults["tools_transform_skew_y"]:
|
||||
self.skewy_entry.set_value(self.app.defaults["tools_transform_skew_y"])
|
||||
else:
|
||||
self.skewy_entry.set_value(0.0)
|
||||
|
||||
if self.app.defaults["tools_transform_scale_x"]:
|
||||
self.scalex_entry.set_value(self.app.defaults["tools_transform_scale_x"])
|
||||
else:
|
||||
self.scalex_entry.set_value(1.0)
|
||||
|
||||
if self.app.defaults["tools_transform_scale_y"]:
|
||||
self.scaley_entry.set_value(self.app.defaults["tools_transform_scale_y"])
|
||||
else:
|
||||
self.scaley_entry.set_value(1.0)
|
||||
|
||||
if self.app.defaults["tools_transform_scale_link"]:
|
||||
self.scale_link_cb.set_value(self.app.defaults["tools_transform_scale_link"])
|
||||
else:
|
||||
self.scale_link_cb.set_value(True)
|
||||
|
||||
if self.app.defaults["tools_transform_scale_reference"]:
|
||||
self.scale_zero_ref_cb.set_value(self.app.defaults["tools_transform_scale_reference"])
|
||||
else:
|
||||
self.scale_zero_ref_cb.set_value(True)
|
||||
|
||||
if self.app.defaults["tools_transform_offset_x"]:
|
||||
self.offx_entry.set_value(self.app.defaults["tools_transform_offset_x"])
|
||||
else:
|
||||
self.offx_entry.set_value(0.0)
|
||||
|
||||
if self.app.defaults["tools_transform_offset_y"]:
|
||||
self.offy_entry.set_value(self.app.defaults["tools_transform_offset_y"])
|
||||
else:
|
||||
self.offy_entry.set_value(0.0)
|
||||
|
||||
if self.app.defaults["tools_transform_mirror_reference"]:
|
||||
self.flip_ref_cb.set_value(self.app.defaults["tools_transform_mirror_reference"])
|
||||
else:
|
||||
self.flip_ref_cb.set_value(False)
|
||||
|
||||
if self.app.defaults["tools_transform_mirror_point"]:
|
||||
self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
|
||||
else:
|
||||
self.flip_ref_entry.set_value((0,0))
|
||||
|
||||
def on_rotate(self):
|
||||
try:
|
||||
|
@ -412,7 +465,7 @@ class ToolTransform(FlatCAMTool):
|
|||
return
|
||||
|
||||
def on_flip_add_coords(self):
|
||||
val = self.app.defaults["global_point_clipboard_format"] % (self.app.pos[0], self.app.pos[1])
|
||||
val = self.app.clipboard.text()
|
||||
self.flip_ref_entry.set_value(val)
|
||||
|
||||
def on_skewx(self):
|
||||
|
@ -595,7 +648,7 @@ class ToolTransform(FlatCAMTool):
|
|||
# add information to the object that it was changed and how much
|
||||
sel_obj.options['rotate'] = num
|
||||
|
||||
self.app.inform.emit('Object(s) were rotated ...')
|
||||
self.app.inform.emit('[success]Rotate done ...')
|
||||
self.app.progress.emit(100)
|
||||
|
||||
except Exception as e:
|
||||
|
@ -656,7 +709,7 @@ class ToolTransform(FlatCAMTool):
|
|||
else:
|
||||
obj.options['mirror_y'] = True
|
||||
obj.plot()
|
||||
self.app.inform.emit('Flipped on the Y axis ...')
|
||||
self.app.inform.emit('[success]Flip on the Y axis done ...')
|
||||
elif axis is 'Y':
|
||||
obj.mirror('Y', (px, py))
|
||||
# add information to the object that it was changed and how much
|
||||
|
@ -666,9 +719,8 @@ class ToolTransform(FlatCAMTool):
|
|||
else:
|
||||
obj.options['mirror_x'] = True
|
||||
obj.plot()
|
||||
self.app.inform.emit('Flipped on the X axis ...')
|
||||
self.app.inform.emit('[success]Flip on the X axis done ...')
|
||||
self.app.object_changed.emit(obj)
|
||||
|
||||
self.app.progress.emit(100)
|
||||
|
||||
except Exception as e:
|
||||
|
@ -715,7 +767,7 @@ class ToolTransform(FlatCAMTool):
|
|||
obj.options['skew_y'] = num
|
||||
obj.plot()
|
||||
self.app.object_changed.emit(obj)
|
||||
self.app.inform.emit('Object(s) were skewed on %s axis ...' % str(axis))
|
||||
self.app.inform.emit('[success]Skew on the %s axis done ...' % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
|
||||
except Exception as e:
|
||||
|
@ -771,7 +823,7 @@ class ToolTransform(FlatCAMTool):
|
|||
obj.options['scale_y'] = yfactor
|
||||
obj.plot()
|
||||
self.app.object_changed.emit(obj)
|
||||
self.app.inform.emit('Object(s) were scaled on %s axis ...' % str(axis))
|
||||
self.app.inform.emit('[success]Scale on the %s axis done ...' % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
except Exception as e:
|
||||
self.app.inform.emit("[ERROR_NOTCL] Due of %s, Scale action was not executed." % str(e))
|
||||
|
@ -816,7 +868,7 @@ class ToolTransform(FlatCAMTool):
|
|||
obj.options['offset_y'] = num
|
||||
obj.plot()
|
||||
self.app.object_changed.emit(obj)
|
||||
self.app.inform.emit('Object(s) were offseted on %s axis ...' % str(axis))
|
||||
self.app.inform.emit('[success]Offset on the %s axis done ...' % str(axis))
|
||||
self.app.progress.emit(100)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
@ -6,12 +6,13 @@ from flatcamTools.ToolFilm import Film
|
|||
from flatcamTools.ToolMove import ToolMove
|
||||
from flatcamTools.ToolDblSided import DblSidedTool
|
||||
|
||||
from flatcamTools.ToolCutOut import ToolCutOut
|
||||
from flatcamTools.ToolCutOut import CutOut
|
||||
from flatcamTools.ToolCalculators import ToolCalculator
|
||||
from flatcamTools.ToolProperties import Properties
|
||||
from flatcamTools.ToolImage import ToolImage
|
||||
from flatcamTools.ToolPaint import ToolPaint
|
||||
from flatcamTools.ToolNonCopperClear import NonCopperClear
|
||||
from flatcamTools.ToolTransform import ToolTransform
|
||||
from flatcamTools.ToolSolderPaste import SolderPaste
|
||||
|
||||
from flatcamTools.ToolShell import FCShell
|
||||
|
|
|
@ -73,7 +73,7 @@ else:
|
|||
excludes=['scipy', 'pytz'],
|
||||
# packages=['OpenGL','numpy','vispy','ortools','google']
|
||||
# packages=['numpy', 'rasterio'] # works for Python 3.7
|
||||
packages = ['opengl', 'numpy', 'google', 'rasterio'] # works for Python 3.6.5 and Python 3.7.1
|
||||
packages = ['opengl', 'numpy', 'rasterio'] # works for Python 3.6.5 and Python 3.7.1
|
||||
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
from FlatCAMPostProc import *
|
||||
|
||||
|
||||
class Paste_1(FlatCAMPostProc_Tools):
|
||||
|
||||
coordinate_format = "%.*f"
|
||||
feedrate_format = '%.*f'
|
||||
|
||||
def start_code(self, p):
|
||||
units = ' ' + str(p['units']).lower()
|
||||
coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
|
||||
|
||||
gcode = ''
|
||||
|
||||
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 += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
|
||||
gcode += '(Feedrate_XY: ' + str(p['frxy']) + units + '/min' + ')\n'
|
||||
gcode += '(Feedrate_Z: ' + str(p['frz']) + units + '/min' + ')\n'
|
||||
gcode += '(Feedrate_Z_Dispense: ' + str(p['frz_dispense']) + units + '/min' + ')\n'
|
||||
|
||||
gcode += '(Z_Dispense_Start: ' + str(p['z_start']) + units + ')\n'
|
||||
gcode += '(Z_Dispense: ' + str(p['z_dispense']) + units + ')\n'
|
||||
gcode += '(Z_Dispense_Stop: ' + str(p['z_stop']) + units + ')\n'
|
||||
gcode += '(Z_Travel: ' + str(p['z_travel']) + units + ')\n'
|
||||
gcode += '(Z Toolchange: ' + str(p['z_toolchange']) + units + ')\n'
|
||||
|
||||
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
|
||||
|
||||
if 'Paste' in p.pp_solderpaste_name:
|
||||
gcode += '(Postprocessor SolderPaste Dispensing Geometry: ' + str(p.pp_solderpaste_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 += '(Spindle Speed FWD: %s RPM)\n' % str(p['speedfwd'])
|
||||
gcode += '(Spindle Speed REV: %s RPM)\n' % str(p['speedrev'])
|
||||
gcode += '(Dwell FWD: %s RPM)\n' % str(p['dwellfwd'])
|
||||
gcode += '(Dwell REV: %s RPM)\n' % str(p['dwellrev'])
|
||||
|
||||
gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
|
||||
gcode += 'G90\n'
|
||||
gcode += 'G94\n'
|
||||
return gcode
|
||||
|
||||
def lift_code(self, p):
|
||||
return 'G00 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_travel']))
|
||||
|
||||
def down_z_start_code(self, p):
|
||||
return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_start']))
|
||||
|
||||
def lift_z_dispense_code(self, p):
|
||||
return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_dispense']))
|
||||
|
||||
def down_z_stop_code(self, p):
|
||||
return 'G01 Z' + self.coordinate_format%(p.coords_decimals, float(p['z_stop']))
|
||||
|
||||
def toolchange_code(self, p):
|
||||
toolchangez = float(p['z_toolchange'])
|
||||
toolchangexy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
|
||||
gcode = ''
|
||||
|
||||
if toolchangexy is not None:
|
||||
toolchangex = toolchangexy[0]
|
||||
toolchangey = toolchangexy[1]
|
||||
|
||||
if p.units.upper() == 'MM':
|
||||
toolC_formatted = format(float(p['toolC']), '.2f')
|
||||
else:
|
||||
toolC_formatted = format(float(p['toolC']), '.4f')
|
||||
|
||||
if toolchangexy is not None:
|
||||
gcode = """
|
||||
G00 Z{toolchangez}
|
||||
G00 X{toolchangex} Y{toolchangey}
|
||||
T{tool}
|
||||
M6
|
||||
(MSG, Change to Tool with Nozzle Dia = {toolC})
|
||||
M0
|
||||
""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
|
||||
toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
|
||||
toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
|
||||
tool=int(int(p.tool)),
|
||||
toolC=toolC_formatted)
|
||||
|
||||
else:
|
||||
gcode = """
|
||||
G00 Z{toolchangez}
|
||||
T{tool}
|
||||
M6
|
||||
(MSG, Change to Tool with Nozzle Dia = {toolC})
|
||||
M0
|
||||
""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
|
||||
tool=int(int(p.tool)),
|
||||
toolC=toolC_formatted)
|
||||
|
||||
return gcode
|
||||
|
||||
def position_code(self, p):
|
||||
return ('X' + self.coordinate_format + ' Y' + self.coordinate_format) % \
|
||||
(p.coords_decimals, p.x, p.coords_decimals, p.y)
|
||||
|
||||
def rapid_code(self, p):
|
||||
return ('G00 ' + self.position_code(p)).format(**p) + '\nG00 Z' + \
|
||||
self.coordinate_format%(p.coords_decimals, float(p['z_travel']))
|
||||
|
||||
def linear_code(self, p):
|
||||
return ('G01 ' + self.position_code(p)).format(**p)
|
||||
|
||||
def end_code(self, p):
|
||||
coords_xy = [float(eval(a)) for a in p['xy_toolchange'].split(",")]
|
||||
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, float(p['z_toolchange'])) + "\n")
|
||||
|
||||
if coords_xy is not None:
|
||||
gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
|
||||
return gcode
|
||||
|
||||
def feedrate_xy_code(self, p):
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frxy'])))
|
||||
|
||||
def feedrate_z_code(self, p):
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz'])))
|
||||
|
||||
def feedrate_z_dispense_code(self, p):
|
||||
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, float(p['frz_dispense'])))
|
||||
|
||||
def spindle_fwd_code(self, p):
|
||||
if p.spindlespeed:
|
||||
return 'M03 S' + str(float(p['speedfwd']))
|
||||
else:
|
||||
return 'M03'
|
||||
|
||||
def spindle_rev_code(self, p):
|
||||
if p.spindlespeed:
|
||||
return 'M04 S' + str(float(p['speedrev']))
|
||||
else:
|
||||
return 'M04'
|
||||
|
||||
def spindle_off_code(self,p):
|
||||
return 'M05'
|
||||
|
||||
def dwell_fwd_code(self, p):
|
||||
if p.dwelltime:
|
||||
return 'G4 P' + str(float(p['dwellfwd']))
|
||||
|
||||
def dwell_rev_code(self, p):
|
||||
if p.dwelltime:
|
||||
return 'G4 P' + str(float(p['dwellrev']))
|
|
@ -14,7 +14,6 @@ apt-get install python3-tk
|
|||
apt-get install libspatialindex-dev
|
||||
apt-get install python3-gdal
|
||||
apt-get install python3-lxml
|
||||
apt-get install python3-ezdxf
|
||||
easy_install3 -U distribute
|
||||
pip3 install --upgrade dill
|
||||
pip3 install --upgrade Shapely
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 201 B |
Binary file not shown.
After Width: | Height: | Size: 271 B |
Binary file not shown.
After Width: | Height: | Size: 732 B |
Binary file not shown.
After Width: | Height: | Size: 495 B |
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
|
@ -225,7 +225,7 @@ class TclCommandGeoCutout(TclCommandSignaled):
|
|||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
try:
|
||||
geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2)
|
||||
geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2, follow=None)
|
||||
except Exception as e:
|
||||
log.debug("TclCommandGeoCutout.execute() --> %s" % str(e))
|
||||
return 'fail'
|
||||
|
|
|
@ -28,7 +28,9 @@ class TclCommandIsolate(TclCommandSignaled):
|
|||
('passes', int),
|
||||
('overlap', float),
|
||||
('combine', int),
|
||||
('outname', str)
|
||||
('outname', str),
|
||||
('follow', str)
|
||||
|
||||
])
|
||||
|
||||
# array of mandatory options for current Tcl command: required = {'name','outname'}
|
||||
|
@ -43,7 +45,8 @@ class TclCommandIsolate(TclCommandSignaled):
|
|||
('passes', 'Passes of tool width.'),
|
||||
('overlap', 'Fraction of tool diameter to overlap passes.'),
|
||||
('combine', 'Combine all passes into one geometry.'),
|
||||
('outname', 'Name of the resulting Geometry object.')
|
||||
('outname', 'Name of the resulting Geometry object.'),
|
||||
('follow', 'Create a Geometry that follows the Gerber path.')
|
||||
]),
|
||||
'examples': []
|
||||
}
|
||||
|
@ -68,6 +71,9 @@ class TclCommandIsolate(TclCommandSignaled):
|
|||
else:
|
||||
timeout = 10000
|
||||
|
||||
if 'follow' not in args:
|
||||
args['follow'] = None
|
||||
|
||||
obj = self.app.collection.get_by_name(name)
|
||||
if obj is None:
|
||||
self.raise_tcl_error("Object not found: %s" % name)
|
||||
|
|
|
@ -17,7 +17,6 @@ class TclCommandOpenGerber(TclCommandSignaled):
|
|||
|
||||
# dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
|
||||
option_types = collections.OrderedDict([
|
||||
('follow', str),
|
||||
('outname', str)
|
||||
])
|
||||
|
||||
|
@ -29,7 +28,6 @@ class TclCommandOpenGerber(TclCommandSignaled):
|
|||
'main': "Opens a Gerber file.",
|
||||
'args': collections.OrderedDict([
|
||||
('filename', 'Path to file to open.'),
|
||||
('follow', 'N If 1, does not create polygons, just follows the gerber path.'),
|
||||
('outname', 'Name of the resulting Gerber object.')
|
||||
]),
|
||||
'examples': []
|
||||
|
@ -54,7 +52,7 @@ class TclCommandOpenGerber(TclCommandSignaled):
|
|||
# Opening the file happens here
|
||||
self.app.progress.emit(30)
|
||||
try:
|
||||
gerber_obj.parse_file(filename, follow=follow)
|
||||
gerber_obj.parse_file(filename)
|
||||
|
||||
except IOError:
|
||||
app_obj.inform.emit("[ERROR_NOTCL] Failed to open file: %s " % filename)
|
||||
|
@ -77,9 +75,8 @@ class TclCommandOpenGerber(TclCommandSignaled):
|
|||
else:
|
||||
outname = filename.split('/')[-1].split('\\')[-1]
|
||||
|
||||
follow = None
|
||||
if 'follow' in args:
|
||||
follow = args['follow']
|
||||
self.raise_tcl_error("The 'follow' parameter is obsolete. To create 'follow' geometry use the 'follow' parameter for the Tcl Command isolate()")
|
||||
|
||||
with self.app.proc_container.new("Opening Gerber"):
|
||||
|
||||
|
|
Loading…
Reference in New Issue