Merged in test_beta_8.906 (pull request #131)

Test beta 8.906
This commit is contained in:
Marius Stanciu 2019-02-05 20:35:21 +00:00
commit c14f28d35e
42 changed files with 5182 additions and 1197 deletions

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
############################################################ ############################################################
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt, QSettings
import FlatCAMApp import FlatCAMApp
from camlib import * from camlib import *
from FlatCAMTool import FlatCAMTool from FlatCAMTool import FlatCAMTool
@ -590,6 +590,7 @@ class FCCircle(FCShapeTool):
self.points.append(point) self.points.append(point)
if len(self.points) == 1: if len(self.points) == 1:
self.draw_app.app.inform.emit("Click on Circle perimeter point to complete ...")
return "Click on perimeter to complete ..." return "Click on perimeter to complete ..."
if len(self.points) == 2: if len(self.points) == 2:
@ -638,9 +639,11 @@ class FCArc(FCShapeTool):
self.points.append(point) self.points.append(point)
if len(self.points) == 1: if len(self.points) == 1:
self.draw_app.app.inform.emit("Click on Start arc point ...")
return "Click on 1st point ..." return "Click on 1st point ..."
if len(self.points) == 2: if len(self.points) == 2:
self.draw_app.app.inform.emit("Click on End arc point to complete ...")
return "Click on 2nd point to complete ..." return "Click on 2nd point to complete ..."
if len(self.points) == 3: if len(self.points) == 3:
@ -850,6 +853,7 @@ class FCPolygon(FCShapeTool):
self.points.append(point) self.points.append(point)
if len(self.points) > 0: if len(self.points) > 0:
self.draw_app.app.inform.emit("Click on next Point or click Right mouse button to complete ...")
return "Click on next point or hit ENTER to complete ..." return "Click on next point or hit ENTER to complete ..."
return "" return ""
@ -1239,7 +1243,7 @@ class FCText(FCShapeTool):
self.geometry = DrawToolShape(affinity.translate(self.text_gui.text_path, xoff=dx, yoff=dy)) self.geometry = DrawToolShape(affinity.translate(self.text_gui.text_path, xoff=dx, yoff=dy))
except Exception as e: except Exception as e:
log.debug("Font geometry is empty or incorrect: %s" % str(e)) log.debug("Font geometry is empty or incorrect: %s" % str(e))
self.draw_app.app.inform.emit("[error]Font not supported. Only Regular, Bold, Italic and BoldItalic are " self.draw_app.app.inform.emit("[ERROR]Font not supported. Only Regular, Bold, Italic and BoldItalic are "
"supported. Error: %s" % str(e)) "supported. Error: %s" % str(e))
self.text_gui.text_path = [] self.text_gui.text_path = []
self.text_gui.hide_tool() self.text_gui.hide_tool()
@ -1416,7 +1420,7 @@ class FCDrillAdd(FCShapeTool):
self.draw_app.tools_table_exc.setCurrentItem(item) self.draw_app.tools_table_exc.setCurrentItem(item)
except KeyError: except KeyError:
self.draw_app.app.inform.emit("[warning_notcl] To add a drill first select a tool") self.draw_app.app.inform.emit("[WARNING_NOTCL] To add a drill first select a tool")
self.draw_app.select_tool("select") self.draw_app.select_tool("select")
return return
@ -1500,7 +1504,7 @@ class FCDrillArray(FCShapeTool):
item = self.draw_app.tools_table_exc.item((self.draw_app.last_tool_selected - 1), 1) item = self.draw_app.tools_table_exc.item((self.draw_app.last_tool_selected - 1), 1)
self.draw_app.tools_table_exc.setCurrentItem(item) self.draw_app.tools_table_exc.setCurrentItem(item)
except KeyError: except KeyError:
self.draw_app.app.inform.emit("[warning_notcl] To add an Drill Array first select a tool in Tool Table") self.draw_app.app.inform.emit("[WARNING_NOTCL] To add an Drill Array first select a tool in Tool Table")
return return
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y), static=True) geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y), static=True)
@ -1525,7 +1529,7 @@ class FCDrillArray(FCShapeTool):
self.flag_for_circ_array = True self.flag_for_circ_array = True
self.set_origin(point) self.set_origin(point)
self.draw_app.app.inform.emit("Click on the circular array Start position") self.draw_app.app.inform.emit("Click on the Drill Circular Array Start position")
else: else:
self.destination = point self.destination = point
self.make() self.make()
@ -1547,10 +1551,10 @@ class FCDrillArray(FCShapeTool):
self.drill_angle = float(self.draw_app.drill_angle_entry.get_value()) self.drill_angle = float(self.draw_app.drill_angle_entry.get_value())
except TypeError: except TypeError:
self.draw_app.app.inform.emit( self.draw_app.app.inform.emit(
"[error_notcl] The value is not Float. Check for comma instead of dot separator.") "[ERROR_NOTCL] The value is not Float. Check for comma instead of dot separator.")
return return
except Exception as e: except Exception as e:
self.draw_app.app.inform.emit("[error_notcl] The value is mistyped. Check the value.") self.draw_app.app.inform.emit("[ERROR_NOTCL] The value is mistyped. Check the value.")
return return
if self.drill_array == 'Linear': if self.drill_array == 'Linear':
@ -1630,7 +1634,7 @@ class FCDrillArray(FCShapeTool):
self.geometry.append(DrawToolShape(geo)) self.geometry.append(DrawToolShape(geo))
else: else:
if (self.drill_angle * self.drill_array_size) > 360: if (self.drill_angle * self.drill_array_size) > 360:
self.draw_app.app.inform.emit("[warning_notcl]Too many drills for the selected spacing angle.") self.draw_app.app.inform.emit("[WARNING_NOTCL]Too many drills for the selected spacing angle.")
return return
radius = distance(self.destination, self.origin) radius = distance(self.destination, self.origin)
@ -1676,7 +1680,7 @@ class FCDrillResize(FCShapeTool):
try: try:
new_dia = self.draw_app.resdrill_entry.get_value() new_dia = self.draw_app.resdrill_entry.get_value()
except: except:
self.draw_app.app.inform.emit("[error_notcl]Resize drill(s) failed. Please enter a diameter for resize.") self.draw_app.app.inform.emit("[ERROR_NOTCL]Resize drill(s) failed. Please enter a diameter for resize.")
return return
if new_dia not in self.draw_app.olddia_newdia: if new_dia not in self.draw_app.olddia_newdia:
@ -1890,9 +1894,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app = app self.app = app
self.canvas = app.plotcanvas self.canvas = app.plotcanvas
self.app.ui.geo_edit_toolbar.setDisabled(disabled)
self.app.ui.snap_max_dist_entry.setDisabled(disabled)
self.app.ui.geo_add_circle_menuitem.triggered.connect(lambda: self.select_tool('circle')) self.app.ui.geo_add_circle_menuitem.triggered.connect(lambda: self.select_tool('circle'))
self.app.ui.geo_add_arc_menuitem.triggered.connect(lambda: self.select_tool('arc')) self.app.ui.geo_add_arc_menuitem.triggered.connect(lambda: self.select_tool('arc'))
self.app.ui.geo_add_rectangle_menuitem.triggered.connect(lambda: self.select_tool('rectangle')) self.app.ui.geo_add_rectangle_menuitem.triggered.connect(lambda: self.select_tool('rectangle'))
@ -1969,6 +1970,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.move_timer = QtCore.QTimer() self.move_timer = QtCore.QTimer()
self.move_timer.setSingleShot(True) self.move_timer.setSingleShot(True)
# this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False
self.key = None # Currently pressed key self.key = None # Currently pressed key
self.geo_key_modifiers = None self.geo_key_modifiers = None
self.x = None # Current mouse cursor pos self.x = None # Current mouse cursor pos
@ -1991,12 +1995,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.tools[tool]["button"].setCheckable(True) # Checkable self.tools[tool]["button"].setCheckable(True) # Checkable
self.app.ui.grid_snap_btn.triggered.connect(self.on_grid_toggled) self.app.ui.grid_snap_btn.triggered.connect(self.on_grid_toggled)
self.app.ui.corner_snap_btn.setCheckable(True)
self.app.ui.corner_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("corner_snap")) self.app.ui.corner_snap_btn.triggered.connect(lambda: self.toolbar_tool_toggle("corner_snap"))
self.options = { self.options = {
"global_gridx": 0.1, "global_gridx": 0.1,
"global_gridy": 0.1, "global_gridy": 0.1,
"snap_max": 0.05, "global_snap_max": 0.05,
"grid_snap": True, "grid_snap": True,
"corner_snap": False, "corner_snap": False,
"grid_gap_link": True "grid_gap_link": True
@ -2009,7 +2014,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.grid_gap_x_entry.setText(str(self.options["global_gridx"])) self.app.ui.grid_gap_x_entry.setText(str(self.options["global_gridx"]))
self.app.ui.grid_gap_y_entry.setText(str(self.options["global_gridy"])) self.app.ui.grid_gap_y_entry.setText(str(self.options["global_gridy"]))
self.app.ui.snap_max_dist_entry.setText(str(self.options["snap_max"])) self.app.ui.snap_max_dist_entry.setText(str(self.options["global_snap_max"]))
self.app.ui.grid_gap_link_cb.setChecked(True) self.app.ui.grid_gap_link_cb.setChecked(True)
self.rtree_index = rtindex.Index() self.rtree_index = rtindex.Index()
@ -2048,10 +2053,27 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.shapes.enabled = True self.shapes.enabled = True
self.tool_shape.enabled = True self.tool_shape.enabled = True
self.app.app_cursor.enabled = True self.app.app_cursor.enabled = True
self.app.ui.snap_max_dist_entry.setDisabled(False)
self.app.ui.snap_max_dist_entry.setEnabled(True)
self.app.ui.corner_snap_btn.setEnabled(True) self.app.ui.corner_snap_btn.setEnabled(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
self.app.ui.geo_editor_menu.setDisabled(False) self.app.ui.geo_editor_menu.setDisabled(False)
self.app.ui.geo_editor_menu.menuAction().setVisible(True)
self.app.ui.update_obj_btn.setEnabled(True)
self.app.ui.g_editor_cmenu.setEnabled(True)
self.app.ui.geo_edit_toolbar.setDisabled(False)
self.app.ui.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_toolbar.setDisabled(False)
# prevent the user to change anything in the Selected Tab while the Geo Editor is active
sel_tab_widget_list = self.app.ui.selected_tab.findChildren(QtWidgets.QWidget)
for w in sel_tab_widget_list:
w.setEnabled(False)
# Tell the App that the editor is active # Tell the App that the editor is active
self.editor_active = True self.editor_active = True
@ -2059,11 +2081,33 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.disconnect_canvas_event_handlers() self.disconnect_canvas_event_handlers()
self.clear() self.clear()
self.app.ui.geo_edit_toolbar.setDisabled(True) self.app.ui.geo_edit_toolbar.setDisabled(True)
self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setDisabled(True) settings = QSettings("Open Source", "FlatCAM")
if settings.contains("theme"):
theme = settings.value('theme', type=str)
if theme == 'standard':
# self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False) self.app.ui.corner_snap_btn.setEnabled(False)
# never deactivate the snap toolbar - MS self.app.ui.snap_magnet.setVisible(False)
# self.app.ui.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool self.app.ui.corner_snap_btn.setVisible(False)
elif theme == 'compact':
# self.app.ui.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
else:
# self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
# set the Editor Toolbar visibility to what was before entering in the Editor
self.app.ui.geo_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
else self.app.ui.geo_edit_toolbar.setVisible(True)
# Disable visuals # Disable visuals
self.shapes.enabled = False self.shapes.enabled = False
@ -2071,6 +2115,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.app_cursor.enabled = False self.app.app_cursor.enabled = False
self.app.ui.geo_editor_menu.setDisabled(True) self.app.ui.geo_editor_menu.setDisabled(True)
self.app.ui.geo_editor_menu.menuAction().setVisible(False)
self.app.ui.update_obj_btn.setEnabled(False)
self.app.ui.g_editor_cmenu.setEnabled(False)
self.app.ui.e_editor_cmenu.setEnabled(False)
# Tell the app that the editor is no longer active # Tell the app that the editor is no longer active
self.editor_active = False self.editor_active = False
@ -2230,9 +2281,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.add_shape(DrawToolShape(shape)) self.add_shape(DrawToolShape(shape))
self.replot() self.replot()
self.app.ui.geo_edit_toolbar.setDisabled(False)
self.app.ui.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_toolbar.setDisabled(False)
# start with GRID toolbar activated # start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() == False: if self.app.ui.grid_snap_btn.isChecked() == False:
@ -2561,7 +2610,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
if event.key.name == 'Escape': if event.key.name == 'Escape':
# TODO: ...? # TODO: ...?
# self.on_tool_select("select") # self.on_tool_select("select")
self.app.inform.emit("[warning_notcl]Cancelled.") self.app.inform.emit("[WARNING_NOTCL]Cancelled.")
self.delete_utility_geometry() self.delete_utility_geometry()
@ -2723,47 +2772,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
# Show Shortcut list # Show Shortcut list
if event.key.name == '`': if event.key.name == '`':
self.on_shortcut_list() self.app.on_shortcut_list()
def on_shortcut_list(self):
msg = '''<b>Shortcut list in Geometry Editor</b><br>
<br>
<b>1:</b> Zoom Fit<br>
<b>2:</b> Zoom Out<br>
<b>3:</b> Zoom In<br>
<b>A:</b> Add an 'Arc'<br>
<b>B:</b> Add a Buffer Geo<br>
<b>C:</b> Copy Geo Item<br>
<b>E:</b> Intersection Tool<br>
<b>G:</b> Grid Snap On/Off<br>
<b>I:</b> Paint Tool<br>
<b>K:</b> Corner Snap On/Off<br>
<b>M:</b> Move Geo Item<br>
<br>
<b>N:</b> Add an 'Polygon'<br>
<b>O:</b> Add a 'Circle'<br>
<b>P:</b> Add a 'Path'<br>
<b>R:</b> Add an 'Rectangle'<br>
<b>S:</b> Substraction Tool<br>
<b>T:</b> Add Text Geometry<br>
<b>U:</b> Union Tool<br>
<br>
<b>X:</b> Cut Path<br>
<br>
<b>~:</b> Show Shortcut List<br>
<br>
<b>Space:</b> Rotate selected Geometry<br>
<b>Enter:</b> Finish Current Action<br>
<b>Escape:</b> Select Tool (Exit any other Tool)<br>
<b>Delete:</b> Delete Obj'''
helpbox =QtWidgets.QMessageBox()
helpbox.setText(msg)
helpbox.setWindowTitle("Help")
helpbox.setWindowIcon(QtGui.QIcon('share/help.png'))
helpbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
helpbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
helpbox.exec_()
def on_canvas_key_release(self, event): def on_canvas_key_release(self, event):
self.key = None self.key = None
@ -2945,7 +2954,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
nearest_pt, shape = self.storage.nearest((x, y)) nearest_pt, shape = self.storage.nearest((x, y))
nearest_pt_distance = distance((x, y), nearest_pt) nearest_pt_distance = distance((x, y), nearest_pt)
if nearest_pt_distance <= self.options["snap_max"]: if nearest_pt_distance <= float(self.options["global_snap_max"]):
snap_distance = nearest_pt_distance snap_distance = nearest_pt_distance
snap_x, snap_y = nearest_pt snap_x, snap_y = nearest_pt
except (StopIteration, AssertionError): except (StopIteration, AssertionError):
@ -3037,7 +3046,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
results = shapes[0].geo results = shapes[0].geo
except Exception as e: except Exception as e:
log.debug("FlatCAMGeoEditor.intersection() --> %s" % str(e)) log.debug("FlatCAMGeoEditor.intersection() --> %s" % str(e))
self.app.inform.emit("[warning_notcl]A selection of at least 2 geo items is required to do Intersection.") self.app.inform.emit("[WARNING_NOTCL]A selection of at least 2 geo items is required to do Intersection.")
self.select_tool('select') self.select_tool('select')
return return
@ -3075,7 +3084,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
if buf_distance < 0: if buf_distance < 0:
self.app.inform.emit( self.app.inform.emit(
"[error_notcl]Negative buffer value is not accepted. Use Buffer interior to generate an 'inside' shape") "[ERROR_NOTCL]Negative buffer value is not accepted. Use Buffer interior to generate an 'inside' shape")
# deselect everything # deselect everything
self.selected = [] self.selected = []
@ -3083,11 +3092,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
return return
if len(selected) == 0: if len(selected) == 0:
self.app.inform.emit("[warning_notcl] Nothing selected for buffering.") self.app.inform.emit("[WARNING_NOTCL] Nothing selected for buffering.")
return return
if not isinstance(buf_distance, float): if not isinstance(buf_distance, float):
self.app.inform.emit("[warning_notcl] Invalid distance for buffering.") self.app.inform.emit("[WARNING_NOTCL] Invalid distance for buffering.")
# deselect everything # deselect everything
self.selected = [] self.selected = []
@ -3097,7 +3106,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
pre_buffer = cascaded_union([t.geo for t in selected]) pre_buffer = cascaded_union([t.geo for t in selected])
results = pre_buffer.buffer(buf_distance - 1e-10, resolution=32, join_style=join_style) results = pre_buffer.buffer(buf_distance - 1e-10, resolution=32, join_style=join_style)
if results.is_empty: if results.is_empty:
self.app.inform.emit("[error_notcl]Failed, the result is empty. Choose a different buffer value.") self.app.inform.emit("[ERROR_NOTCL]Failed, the result is empty. Choose a different buffer value.")
# deselect everything # deselect everything
self.selected = [] self.selected = []
self.replot() self.replot()
@ -3112,18 +3121,18 @@ class FlatCAMGeoEditor(QtCore.QObject):
if buf_distance < 0: if buf_distance < 0:
self.app.inform.emit( self.app.inform.emit(
"[error_notcl]Negative buffer value is not accepted. Use Buffer interior to generate an 'inside' shape") "[ERROR_NOTCL]Negative buffer value is not accepted. Use Buffer interior to generate an 'inside' shape")
# deselect everything # deselect everything
self.selected = [] self.selected = []
self.replot() self.replot()
return return
if len(selected) == 0: if len(selected) == 0:
self.app.inform.emit("[warning_notcl] Nothing selected for buffering.") self.app.inform.emit("[WARNING_NOTCL] Nothing selected for buffering.")
return return
if not isinstance(buf_distance, float): if not isinstance(buf_distance, float):
self.app.inform.emit("[warning_notcl] Invalid distance for buffering.") self.app.inform.emit("[WARNING_NOTCL] Invalid distance for buffering.")
# deselect everything # deselect everything
self.selected = [] self.selected = []
self.replot() self.replot()
@ -3132,7 +3141,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
pre_buffer = cascaded_union([t.geo for t in selected]) pre_buffer = cascaded_union([t.geo for t in selected])
results = pre_buffer.buffer(-buf_distance + 1e-10, resolution=32, join_style=join_style) results = pre_buffer.buffer(-buf_distance + 1e-10, resolution=32, join_style=join_style)
if results.is_empty: if results.is_empty:
self.app.inform.emit("[error_notcl]Failed, the result is empty. Choose a smaller buffer value.") self.app.inform.emit("[ERROR_NOTCL]Failed, the result is empty. Choose a smaller buffer value.")
# deselect everything # deselect everything
self.selected = [] self.selected = []
self.replot() self.replot()
@ -3152,7 +3161,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
# return # return
# #
# if not isinstance(buf_distance, float): # if not isinstance(buf_distance, float):
# self.app.inform.emit("[warning] Invalid distance for buffering.") # self.app.inform.emit("[WARNING] Invalid distance for buffering.")
# return # return
# #
# pre_buffer = cascaded_union([t.geo for t in selected]) # pre_buffer = cascaded_union([t.geo for t in selected])
@ -3182,7 +3191,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
selected = self.get_selected() selected = self.get_selected()
if buf_distance < 0: if buf_distance < 0:
self.app.inform.emit("[error_notcl]Negative buffer value is not accepted. " self.app.inform.emit("[ERROR_NOTCL]Negative buffer value is not accepted. "
"Use Buffer interior to generate an 'inside' shape") "Use Buffer interior to generate an 'inside' shape")
# deselect everything # deselect everything
self.selected = [] self.selected = []
@ -3190,11 +3199,11 @@ class FlatCAMGeoEditor(QtCore.QObject):
return return
if len(selected) == 0: if len(selected) == 0:
self.app.inform.emit("[warning_notcl] Nothing selected for buffering.") self.app.inform.emit("[WARNING_NOTCL] Nothing selected for buffering.")
return return
if not isinstance(buf_distance, float): if not isinstance(buf_distance, float):
self.app.inform.emit("[warning_notcl] Invalid distance for buffering.") self.app.inform.emit("[WARNING_NOTCL] Invalid distance for buffering.")
# deselect everything # deselect everything
self.selected = [] self.selected = []
self.replot() self.replot()
@ -3203,7 +3212,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
pre_buffer = cascaded_union([t.geo for t in selected]) pre_buffer = cascaded_union([t.geo for t in selected])
results = pre_buffer.buffer(buf_distance - 1e-10, resolution=32, join_style=join_style) results = pre_buffer.buffer(buf_distance - 1e-10, resolution=32, join_style=join_style)
if results.is_empty: if results.is_empty:
self.app.inform.emit("[error_notcl]Failed, the result is empty. Choose a different buffer value.") self.app.inform.emit("[ERROR_NOTCL]Failed, the result is empty. Choose a different buffer value.")
# deselect everything # deselect everything
self.selected = [] self.selected = []
self.replot() self.replot()
@ -3221,13 +3230,13 @@ class FlatCAMGeoEditor(QtCore.QObject):
# selected = self.get_selected() # selected = self.get_selected()
# #
# if len(selected) == 0: # if len(selected) == 0:
# self.app.inform.emit("[warning] Nothing selected for painting.") # self.app.inform.emit("[WARNING] Nothing selected for painting.")
# return # return
# #
# for param in [tooldia, overlap, margin]: # for param in [tooldia, overlap, margin]:
# if not isinstance(param, float): # if not isinstance(param, float):
# param_name = [k for k, v in locals().items() if v is param][0] # param_name = [k for k, v in locals().items() if v is param][0]
# self.app.inform.emit("[warning] Invalid value for {}".format(param)) # self.app.inform.emit("[WARNING] Invalid value for {}".format(param))
# #
# # Todo: Check for valid method. # # Todo: Check for valid method.
# #
@ -3279,19 +3288,19 @@ class FlatCAMGeoEditor(QtCore.QObject):
selected = self.get_selected() selected = self.get_selected()
if len(selected) == 0: if len(selected) == 0:
self.app.inform.emit("[warning_notcl]Nothing selected for painting.") self.app.inform.emit("[WARNING_NOTCL]Nothing selected for painting.")
return return
for param in [tooldia, overlap, margin]: for param in [tooldia, overlap, margin]:
if not isinstance(param, float): if not isinstance(param, float):
param_name = [k for k, v in locals().items() if v is param][0] param_name = [k for k, v in locals().items() if v is param][0]
self.app.inform.emit("[warning] Invalid value for {}".format(param)) self.app.inform.emit("[WARNING] Invalid value for {}".format(param))
results = [] results = []
if tooldia >= overlap: if tooldia >= overlap:
self.app.inform.emit( self.app.inform.emit(
"[error_notcl] Could not do Paint. Overlap value has to be less than Tool Dia value.") "[ERROR_NOTCL] Could not do Paint. Overlap value has to be less than Tool Dia value.")
return return
def recurse(geometry, reset=True): def recurse(geometry, reset=True):
@ -3350,7 +3359,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
except Exception as e: except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e)) log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit( self.app.inform.emit(
"[error] Could not do Paint. Try a different combination of parameters. " "[ERROR] Could not do Paint. Try a different combination of parameters. "
"Or a different method of Paint\n%s" % str(e)) "Or a different method of Paint\n%s" % str(e))
return return
@ -3694,6 +3703,9 @@ class FlatCAMExcEditor(QtCore.QObject):
# this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False) # this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False)
self.launched_from_shortcuts = False self.launched_from_shortcuts = False
# this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False
self.app.ui.delete_drill_btn.triggered.connect(self.on_delete_btn) self.app.ui.delete_drill_btn.triggered.connect(self.on_delete_btn)
self.name_entry.returnPressed.connect(self.on_name_activate) self.name_entry.returnPressed.connect(self.on_name_activate)
self.addtool_btn.clicked.connect(self.on_tool_add) self.addtool_btn.clicked.connect(self.on_tool_add)
@ -3704,6 +3716,17 @@ class FlatCAMExcEditor(QtCore.QObject):
self.drill_axis_radio.activated_custom.connect(self.on_linear_angle_radio) self.drill_axis_radio.activated_custom.connect(self.on_linear_angle_radio)
self.app.ui.exc_add_array_drill_menuitem.triggered.connect(self.exc_add_drill_array)
self.app.ui.exc_add_drill_menuitem.triggered.connect(self.exc_add_drill)
self.app.ui.exc_resize_drill_menuitem.triggered.connect(self.exc_resize_drills)
self.app.ui.exc_copy_drill_menuitem.triggered.connect(self.exc_copy_drills)
self.app.ui.exc_delete_drill_menuitem.triggered.connect(self.on_delete_btn)
self.app.ui.exc_move_drill_menuitem.triggered.connect(self.exc_move_drills)
# Init GUI
self.drill_array_size_entry.set_value(5) self.drill_array_size_entry.set_value(5)
self.drill_pitch_entry.set_value(2.54) self.drill_pitch_entry.set_value(2.54)
self.drill_angle_entry.set_value(12) self.drill_angle_entry.set_value(12)
@ -4046,7 +4069,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# each time a tool diameter is edited or added # each time a tool diameter is edited or added
self.olddia_newdia[tool_dia] = tool_dia self.olddia_newdia[tool_dia] = tool_dia
else: else:
self.app.inform.emit("[warning_notcl]Tool already in the original or actual tool list.\n" self.app.inform.emit("[WARNING_NOTCL]Tool already in the original or actual tool list.\n"
"Save and reedit Excellon if you need to add this tool. ") "Save and reedit Excellon if you need to add this tool. ")
return return
@ -4084,7 +4107,7 @@ class FlatCAMExcEditor(QtCore.QObject):
else: else:
deleted_tool_dia_list.append(float('%.4f' % dia)) deleted_tool_dia_list.append(float('%.4f' % dia))
except: except:
self.app.inform.emit("[warning_notcl]Select a tool in Tool Table") self.app.inform.emit("[WARNING_NOTCL]Select a tool in Tool Table")
return return
for deleted_tool_dia in deleted_tool_dia_list: for deleted_tool_dia in deleted_tool_dia_list:
@ -4172,8 +4195,26 @@ class FlatCAMExcEditor(QtCore.QObject):
self.shapes.enabled = True self.shapes.enabled = True
self.tool_shape.enabled = True self.tool_shape.enabled = True
# self.app.app_cursor.enabled = True # self.app.app_cursor.enabled = True
self.app.ui.snap_max_dist_entry.setDisabled(False)
self.app.ui.snap_max_dist_entry.setEnabled(True)
self.app.ui.corner_snap_btn.setEnabled(True) self.app.ui.corner_snap_btn.setEnabled(True)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
self.app.ui.exc_editor_menu.setDisabled(False)
self.app.ui.exc_editor_menu.menuAction().setVisible(True)
self.app.ui.update_obj_btn.setEnabled(True)
self.app.ui.e_editor_cmenu.setEnabled(True)
self.app.ui.exc_edit_toolbar.setDisabled(False)
self.app.ui.exc_edit_toolbar.setVisible(True)
# self.app.ui.snap_toolbar.setDisabled(False)
# start with GRID toolbar activated
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
# Tell the App that the editor is active # Tell the App that the editor is active
self.editor_active = True self.editor_active = True
@ -4181,9 +4222,35 @@ class FlatCAMExcEditor(QtCore.QObject):
self.disconnect_canvas_event_handlers() self.disconnect_canvas_event_handlers()
self.clear() self.clear()
self.app.ui.exc_edit_toolbar.setDisabled(True) self.app.ui.exc_edit_toolbar.setDisabled(True)
self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setDisabled(True) settings = QSettings("Open Source", "FlatCAM")
if settings.contains("theme"):
theme = settings.value('theme', type=str)
if theme == 'standard':
# self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False) self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
elif theme == 'compact':
# self.app.ui.exc_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(True)
self.app.ui.corner_snap_btn.setVisible(True)
else:
# self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
# set the Editor Toolbar visibility to what was before entering in the Editor
self.app.ui.exc_edit_toolbar.setVisible(False) if self.toolbar_old_state is False \
else self.app.ui.exc_edit_toolbar.setVisible(True)
# Disable visuals # Disable visuals
self.shapes.enabled = False self.shapes.enabled = False
@ -4193,6 +4260,14 @@ class FlatCAMExcEditor(QtCore.QObject):
# Tell the app that the editor is no longer active # Tell the app that the editor is no longer active
self.editor_active = False self.editor_active = False
self.app.ui.exc_editor_menu.setDisabled(True)
self.app.ui.exc_editor_menu.menuAction().setVisible(False)
self.app.ui.update_obj_btn.setEnabled(False)
self.app.ui.g_editor_cmenu.setEnabled(False)
self.app.ui.e_editor_cmenu.setEnabled(False)
# Show original geometry # Show original geometry
if self.exc_obj: if self.exc_obj:
self.exc_obj.visible = True self.exc_obj.visible = True
@ -4250,7 +4325,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# self.storage = FlatCAMExcEditor.make_storage() # self.storage = FlatCAMExcEditor.make_storage()
self.replot() self.replot()
def edit_exc_obj(self, exc_obj): def edit_fcexcellon(self, exc_obj):
""" """
Imports the geometry from the given FlatCAM Excellon object Imports the geometry from the given FlatCAM Excellon object
into the editor. into the editor.
@ -4298,15 +4373,8 @@ class FlatCAMExcEditor(QtCore.QObject):
self.storage_dict[tool_dia] = storage_elem self.storage_dict[tool_dia] = storage_elem
self.replot() self.replot()
self.app.ui.exc_edit_toolbar.setDisabled(False)
self.app.ui.exc_edit_toolbar.setVisible(True)
self.app.ui.snap_toolbar.setDisabled(False)
# start with GRID toolbar activated def update_fcexcellon(self, exc_obj):
if self.app.ui.grid_snap_btn.isChecked() is False:
self.app.ui.grid_snap_btn.trigger()
def update_exc_obj(self, exc_obj):
""" """
Create a new Excellon object that contain the edited content of the source Excellon object Create a new Excellon object that contain the edited content of the source Excellon object
@ -4411,6 +4479,21 @@ class FlatCAMExcEditor(QtCore.QObject):
# Switch notebook to Selected page # Switch notebook to Selected page
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
def update_options(self, obj):
try:
if not obj.options:
obj.options = {}
obj.options['xmin'] = 0
obj.options['ymin'] = 0
obj.options['xmax'] = 0
obj.options['ymax'] = 0
return True
else:
return False
except AttributeError:
obj.options = {}
return True
def new_edited_excellon(self, outname): def new_edited_excellon(self, outname):
""" """
Creates a new Excellon object for the edited Excellon. Thread-safe. Creates a new Excellon object for the edited Excellon. Thread-safe.
@ -4430,14 +4513,15 @@ class FlatCAMExcEditor(QtCore.QObject):
excellon_obj.drills = self.new_drills excellon_obj.drills = self.new_drills
excellon_obj.tools = self.new_tools excellon_obj.tools = self.new_tools
excellon_obj.slots = self.new_slots excellon_obj.slots = self.new_slots
excellon_obj.options['name'] = outname
try: try:
excellon_obj.create_geometry() excellon_obj.create_geometry()
except KeyError: except KeyError:
self.app.inform.emit( self.app.inform.emit(
"[error_notcl] There are no Tools definitions in the file. Aborting Excellon creation.") "[ERROR_NOTCL] There are no Tools definitions in the file. Aborting Excellon creation.")
except: except:
msg = "[error] An internal error has ocurred. See shell.\n" msg = "[ERROR] An internal error has ocurred. See shell.\n"
msg += traceback.format_exc() msg += traceback.format_exc()
app_obj.inform.emit(msg) app_obj.inform.emit(msg)
raise raise
@ -4469,7 +4553,7 @@ class FlatCAMExcEditor(QtCore.QObject):
# self.draw_app.select_tool('select') # self.draw_app.select_tool('select')
self.complete = True self.complete = True
current_tool = 'select' current_tool = 'select'
self.app.inform.emit("[warning_notcl]Cancelled. There is no Tool/Drill selected") self.app.inform.emit("[WARNING_NOTCL]Cancelled. There is no Tool/Drill selected")
# This is to make the group behave as radio group # This is to make the group behave as radio group
if current_tool in self.tools_exc: if current_tool in self.tools_exc:
@ -4813,7 +4897,7 @@ class FlatCAMExcEditor(QtCore.QObject):
if event.key.name == 'Escape': if event.key.name == 'Escape':
# TODO: ...? # TODO: ...?
# self.on_tool_select("select") # self.on_tool_select("select")
self.app.inform.emit("[warning_notcl]Cancelled.") self.app.inform.emit("[WARNING_NOTCL]Cancelled.")
self.delete_utility_geometry() self.delete_utility_geometry()
@ -4830,7 +4914,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.delete_selected() self.delete_selected()
self.replot() self.replot()
else: else:
self.app.inform.emit("[warning_notcl]Cancelled. Nothing selected to delete.") self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.")
return return
if event.key == '1': if event.key == '1':
@ -4862,7 +4946,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.on_tool_select('copy') self.on_tool_select('copy')
self.active_tool.set_origin((self.snap_x, self.snap_y)) self.active_tool.set_origin((self.snap_x, self.snap_y))
else: else:
self.app.inform.emit("[warning_notcl]Cancelled. Nothing selected to copy.") self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to copy.")
return return
# Add Drill Hole Tool # Add Drill Hole Tool
@ -4899,7 +4983,7 @@ class FlatCAMExcEditor(QtCore.QObject):
self.on_tool_select('move') self.on_tool_select('move')
self.active_tool.set_origin((self.snap_x, self.snap_y)) self.active_tool.set_origin((self.snap_x, self.snap_y))
else: else:
self.app.inform.emit("[warning_notcl]Cancelled. Nothing selected to move.") self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to move.")
return return
# Resize Tool # Resize Tool
@ -4923,39 +5007,9 @@ class FlatCAMExcEditor(QtCore.QObject):
# Show Shortcut list # Show Shortcut list
if event.key.name == '`': if event.key.name == '`':
self.on_shortcut_list() self.app.on_shortcut_list()
return return
def on_shortcut_list(self):
msg = '''<b>Shortcut list in Geometry Editor</b><br>
<br>
<b>1:</b> Zoom Fit<br>
<b>2:</b> Zoom Out<br>
<b>3:</b> Zoom In<br>
<b>A:</b> Add an 'Drill Array'<br>
<b>C:</b> Copy Drill Hole<br>
<b>D:</b> Add an Drill Hole<br>
<b>G:</b> Grid Snap On/Off<br>
<b>K:</b> Corner Snap On/Off<br>
<b>M:</b> Move Drill Hole<br>
<br>
<b>R:</b> Resize a 'Drill Hole'<br>
<b>S:</b> Select Tool Active<br>
<br>
<b>~:</b> Show Shortcut List<br>
<br>
<b>Enter:</b> Finish Current Action<br>
<b>Escape:</b> Abort Current Action<br>
<b>Delete:</b> Delete Drill Hole'''
helpbox =QtWidgets.QMessageBox()
helpbox.setText(msg)
helpbox.setWindowTitle("Help")
helpbox.setWindowIcon(QtGui.QIcon('share/help.png'))
helpbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
helpbox.setDefaultButton(QtWidgets.QMessageBox.Ok)
helpbox.exec_()
def on_canvas_key_release(self, event): def on_canvas_key_release(self, event):
self.key = None self.key = None
@ -5197,10 +5251,18 @@ class FlatCAMExcEditor(QtCore.QObject):
self.select_tool('add_array') self.select_tool('add_array')
return return
def exc_resize_drills(self):
self.select_tool('resize')
return
def exc_copy_drills(self): def exc_copy_drills(self):
self.select_tool('copy') self.select_tool('copy')
return return
def exc_move_drills(self):
self.select_tool('move')
return
def distance(pt1, pt2): def distance(pt1, pt2):
return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2) return sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
from PyQt5 import QtGui, QtCore, QtWidgets, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from copy import copy from copy import copy
import re import re
import logging import logging
@ -550,6 +552,478 @@ class FCTab(QtWidgets.QTabWidget):
self.tabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None) self.tabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
class FCDetachableTab(QtWidgets.QTabWidget):
# From here: https://stackoverflow.com/questions/47267195/in-pyqt4-is-it-possible-to-detach-tabs-from-a-qtabwidget
def __init__(self, protect=None, protect_by_name=None, parent=None):
super().__init__()
self.tabBar = self.FCTabBar(self)
self.tabBar.onDetachTabSignal.connect(self.detachTab)
self.tabBar.onMoveTabSignal.connect(self.moveTab)
self.tabBar.detachedTabDropSignal.connect(self.detachedTabDrop)
self.setTabBar(self.tabBar)
# Used to keep a reference to detached tabs since their QMainWindow
# does not have a parent
self.detachedTabs = {}
# a way to make sure that tabs can't be closed after they attach to the parent tab
self.protect_tab = True if protect is not None and protect is True else False
self.protect_by_name = protect_by_name if isinstance(protect_by_name, list) else None
# Close all detached tabs if the application is closed explicitly
QtWidgets.qApp.aboutToQuit.connect(self.closeDetachedTabs) # @UndefinedVariable
# used by the property self.useOldIndex(param)
self.use_old_index = None
self.old_index = None
self.setTabsClosable(True)
self.tabCloseRequested.connect(self.closeTab)
def useOldIndex(self, param):
if param:
self.use_old_index = True
else:
self.use_old_index = False
def deleteTab(self, currentIndex):
widget = self.widget(currentIndex)
if widget is not None:
widget.deleteLater()
self.removeTab(currentIndex)
def closeTab(self, currentIndex):
self.removeTab(currentIndex)
def protectTab(self, currentIndex):
# self.FCTabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
self.tabBar.setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
##
# The default movable functionality of QTabWidget must remain disabled
# so as not to conflict with the added features
def setMovable(self, movable):
pass
##
# Move a tab from one position (index) to another
#
# @param fromIndex the original index location of the tab
# @param toIndex the new index location of the tab
@pyqtSlot(int, int)
def moveTab(self, fromIndex, toIndex):
widget = self.widget(fromIndex)
icon = self.tabIcon(fromIndex)
text = self.tabText(fromIndex)
self.removeTab(fromIndex)
self.insertTab(toIndex, widget, icon, text)
self.setCurrentIndex(toIndex)
##
# Detach the tab by removing it's contents and placing them in
# a DetachedTab window
#
# @param index the index location of the tab to be detached
# @param point the screen position for creating the new DetachedTab window
@pyqtSlot(int, QtCore.QPoint)
def detachTab(self, index, point):
self.old_index = index
# Get the tab content
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
try:
contentWidgetRect = contentWidget.frameGeometry()
except AttributeError:
return
# Create a new detached tab window
detachedTab = self.FCDetachedTab(name, contentWidget)
detachedTab.setWindowModality(QtCore.Qt.NonModal)
detachedTab.setWindowIcon(icon)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.onCloseSignal.connect(self.attachTab)
detachedTab.onDropSignal.connect(self.tabBar.detachedTabDrop)
detachedTab.move(point)
detachedTab.show()
# Create a reference to maintain access to the detached tab
self.detachedTabs[name] = detachedTab
##
# Re-attach the tab by removing the content from the DetachedTab window,
# closing it, and placing the content back into the DetachableTabWidget
#
# @param contentWidget the content widget from the DetachedTab window
# @param name the name of the detached tab
# @param icon the window icon for the detached tab
# @param insertAt insert the re-attached tab at the given index
def attachTab(self, contentWidget, name, icon, insertAt=None):
# Make the content widget a child of this widget
contentWidget.setParent(self)
# Remove the reference
del self.detachedTabs[name]
# helps in restoring the tab to the same index that it was before was detached
insert_index = self.old_index if self.use_old_index is True else insertAt
# Create an image from the given icon (for comparison)
if not icon.isNull():
try:
tabIconPixmap = icon.pixmap(icon.availableSizes()[0])
tabIconImage = tabIconPixmap.toImage()
except IndexError:
tabIconImage = None
else:
tabIconImage = None
# Create an image of the main window icon (for comparison)
if not icon.isNull():
try:
windowIconPixmap = self.window().windowIcon().pixmap(icon.availableSizes()[0])
windowIconImage = windowIconPixmap.toImage()
except IndexError:
windowIconImage = None
else:
windowIconImage = None
# Determine if the given image and the main window icon are the same.
# If they are, then do not add the icon to the tab
if tabIconImage == windowIconImage:
if insert_index is None:
index = self.addTab(contentWidget, name)
else:
index = self.insertTab(insert_index, contentWidget, name)
else:
if insert_index is None:
index = self.addTab(contentWidget, icon, name)
else:
index = self.insertTab(insert_index, contentWidget, icon, name)
# on reattaching the tab if protect is true then the closure button is not added
if self.protect_tab is True:
self.protectTab(index)
# on reattaching the tab disable the closure button for the tabs with the name in the self.protect_by_name list
if self.protect_by_name is not None:
for tab_name in self.protect_by_name:
for index in range(self.count()):
if str(tab_name) == str(self.tabText(index)):
self.protectTab(index)
# Make this tab the current tab
if index > -1:
self.setCurrentIndex(insert_index) if self.use_old_index else self.setCurrentIndex(index)
##
# Remove the tab with the given name, even if it is detached
#
# @param name the name of the tab to be removed
def removeTabByName(self, name):
# Remove the tab if it is attached
attached = False
for index in range(self.count()):
if str(name) == str(self.tabText(index)):
self.removeTab(index)
attached = True
break
# If the tab is not attached, close it's window and
# remove the reference to it
if not attached:
for key in self.detachedTabs:
if str(name) == str(key):
self.detachedTabs[key].onCloseSignal.disconnect()
self.detachedTabs[key].close()
del self.detachedTabs[key]
break
##
# Handle dropping of a detached tab inside the DetachableTabWidget
#
# @param name the name of the detached tab
# @param index the index of an existing tab (if the tab bar
# determined that the drop occurred on an
# existing tab)
# @param dropPos the mouse cursor position when the drop occurred
@QtCore.pyqtSlot(str, int, QtCore.QPoint)
def detachedTabDrop(self, name, index, dropPos):
# If the drop occurred on an existing tab, insert the detached
# tab at the existing tab's location
if index > -1:
# Create references to the detached tab's content and icon
contentWidget = self.detachedTabs[name].contentWidget
icon = self.detachedTabs[name].windowIcon()
# Disconnect the detached tab's onCloseSignal so that it
# does not try to re-attach automatically
self.detachedTabs[name].onCloseSignal.disconnect()
# Close the detached
self.detachedTabs[name].close()
# Re-attach the tab at the given index
self.attachTab(contentWidget, name, icon, index)
# If the drop did not occur on an existing tab, determine if the drop
# occurred in the tab bar area (the area to the side of the QTabBar)
else:
# Find the drop position relative to the DetachableTabWidget
tabDropPos = self.mapFromGlobal(dropPos)
# If the drop position is inside the DetachableTabWidget...
if self.rect().contains(tabDropPos):
# If the drop position is inside the tab bar area (the
# area to the side of the QTabBar) or there are not tabs
# currently attached...
if tabDropPos.y() < self.tabBar.height() or self.count() == 0:
# Close the detached tab and allow it to re-attach
# automatically
self.detachedTabs[name].close()
##
# Close all tabs that are currently detached.
def closeDetachedTabs(self):
listOfDetachedTabs = []
for key in self.detachedTabs:
listOfDetachedTabs.append(self.detachedTabs[key])
for detachedTab in listOfDetachedTabs:
detachedTab.close()
##
# When a tab is detached, the contents are placed into this QMainWindow. The tab
# can be re-attached by closing the dialog or by dragging the window into the tab bar
class FCDetachedTab(QtWidgets.QMainWindow):
onCloseSignal = pyqtSignal(QtWidgets.QWidget, str, QtGui.QIcon)
onDropSignal = pyqtSignal(str, QtCore.QPoint)
def __init__(self, name, contentWidget):
QtWidgets.QMainWindow.__init__(self, None)
self.setObjectName(name)
self.setWindowTitle(name)
self.contentWidget = contentWidget
self.setCentralWidget(self.contentWidget)
self.contentWidget.show()
self.windowDropFilter = self.WindowDropFilter()
self.installEventFilter(self.windowDropFilter)
self.windowDropFilter.onDropSignal.connect(self.windowDropSlot)
##
# Handle a window drop event
#
# @param dropPos the mouse cursor position of the drop
@QtCore.pyqtSlot(QtCore.QPoint)
def windowDropSlot(self, dropPos):
self.onDropSignal.emit(self.objectName(), dropPos)
##
# If the window is closed, emit the onCloseSignal and give the
# content widget back to the DetachableTabWidget
#
# @param event a close event
def closeEvent(self, event):
self.onCloseSignal.emit(self.contentWidget, self.objectName(), self.windowIcon())
##
# An event filter class to detect a QMainWindow drop event
class WindowDropFilter(QtCore.QObject):
onDropSignal = pyqtSignal(QtCore.QPoint)
def __init__(self):
QtCore.QObject.__init__(self)
self.lastEvent = None
##
# Detect a QMainWindow drop event by looking for a NonClientAreaMouseMove (173)
# event that immediately follows a Move event
#
# @param obj the object that generated the event
# @param event the current event
def eventFilter(self, obj, event):
# If a NonClientAreaMouseMove (173) event immediately follows a Move event...
if self.lastEvent == QtCore.QEvent.Move and event.type() == 173:
# Determine the position of the mouse cursor and emit it with the
# onDropSignal
mouseCursor = QtGui.QCursor()
dropPos = mouseCursor.pos()
self.onDropSignal.emit(dropPos)
self.lastEvent = event.type()
return True
else:
self.lastEvent = event.type()
return False
class FCTabBar(QtWidgets.QTabBar):
onDetachTabSignal = pyqtSignal(int, QtCore.QPoint)
onMoveTabSignal = pyqtSignal(int, int)
detachedTabDropSignal = pyqtSignal(str, int, QtCore.QPoint)
def __init__(self, parent=None):
QtWidgets.QTabBar.__init__(self, parent)
self.setAcceptDrops(True)
self.setElideMode(QtCore.Qt.ElideRight)
self.setSelectionBehaviorOnRemove(QtWidgets.QTabBar.SelectLeftTab)
self.dragStartPos = QtCore.QPoint()
self.dragDropedPos = QtCore.QPoint()
self.mouseCursor = QtGui.QCursor()
self.dragInitiated = False
# Send the onDetachTabSignal when a tab is double clicked
#
# @param event a mouse double click event
def mouseDoubleClickEvent(self, event):
event.accept()
self.onDetachTabSignal.emit(self.tabAt(event.pos()), self.mouseCursor.pos())
# Set the starting position for a drag event when the mouse button is pressed
#
# @param event a mouse press event
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.dragStartPos = event.pos()
self.dragDropedPos.setX(0)
self.dragDropedPos.setY(0)
self.dragInitiated = False
QtWidgets.QTabBar.mousePressEvent(self, event)
# Determine if the current movement is a drag. If it is, convert it into a QDrag. If the
# drag ends inside the tab bar, emit an onMoveTabSignal. If the drag ends outside the tab
# bar, emit an onDetachTabSignal.
#
# @param event a mouse move event
def mouseMoveEvent(self, event):
# Determine if the current movement is detected as a drag
if not self.dragStartPos.isNull() and ((event.pos() - self.dragStartPos).manhattanLength() < QtWidgets.QApplication.startDragDistance()):
self.dragInitiated = True
# If the current movement is a drag initiated by the left button
if (((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated):
# Stop the move event
finishMoveEvent = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier)
QtWidgets.QTabBar.mouseMoveEvent(self, finishMoveEvent)
# Convert the move event into a drag
drag = QtGui.QDrag(self)
mimeData = QtCore.QMimeData()
# mimeData.setData('action', 'application/tab-detach')
drag.setMimeData(mimeData)
# screen = QScreen(self.parentWidget().currentWidget().winId())
# Create the appearance of dragging the tab content
try:
pixmap = self.parent().widget(self.tabAt(self.dragStartPos)).grab()
except Exception as e:
log.debug("GUIElements.FCDetachable. FCTabBar.mouseMoveEvent() --> %s" % str(e))
return
targetPixmap = QtGui.QPixmap(pixmap.size())
targetPixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(targetPixmap)
painter.setOpacity(0.85)
painter.drawPixmap(0, 0, pixmap)
painter.end()
drag.setPixmap(targetPixmap)
# Initiate the drag
dropAction = drag.exec_(QtCore.Qt.MoveAction | QtCore.Qt.CopyAction)
# For Linux: Here, drag.exec_() will not return MoveAction on Linux. So it
# must be set manually
if self.dragDropedPos.x() != 0 and self.dragDropedPos.y() != 0:
dropAction = QtCore.Qt.MoveAction
# If the drag completed outside of the tab bar, detach the tab and move
# the content to the current cursor position
if dropAction == QtCore.Qt.IgnoreAction:
event.accept()
self.onDetachTabSignal.emit(self.tabAt(self.dragStartPos), self.mouseCursor.pos())
# Else if the drag completed inside the tab bar, move the selected tab to the new position
elif dropAction == QtCore.Qt.MoveAction:
if not self.dragDropedPos.isNull():
event.accept()
self.onMoveTabSignal.emit(self.tabAt(self.dragStartPos), self.tabAt(self.dragDropedPos))
else:
QtWidgets.QTabBar.mouseMoveEvent(self, event)
# Determine if the drag has entered a tab position from another tab position
#
# @param event a drag enter event
def dragEnterEvent(self, event):
mimeData = event.mimeData()
# formats = mcd imeData.formats()
# if formats.contains('action') and mimeData.data('action') == 'application/tab-detach':
# event.acceptProposedAction()
QtWidgets.QTabBar.dragMoveEvent(self, event)
# Get the position of the end of the drag
#
# @param event a drop event
def dropEvent(self, event):
self.dragDropedPos = event.pos()
QtWidgets.QTabBar.dropEvent(self, event)
# Determine if the detached tab drop event occurred on an existing tab,
# then send the event to the DetachableTabWidget
def detachedTabDrop(self, name, dropPos):
tabDropPos = self.mapFromGlobal(dropPos)
index = self.tabAt(tabDropPos)
self.detachedTabDropSignal.emit(name, index, dropPos)
class VerticalScrollArea(QtWidgets.QScrollArea): class VerticalScrollArea(QtWidgets.QScrollArea):
""" """
This widget extends QtGui.QScrollArea to make a vertical-only This widget extends QtGui.QScrollArea to make a vertical-only

View File

@ -46,18 +46,24 @@ class KeySensitiveListView(QtWidgets.QTreeView):
event.ignore() event.ignore()
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
self.setDropIndicatorShown(True)
if event.mimeData().hasUrls: if event.mimeData().hasUrls:
event.accept() event.accept()
else: else:
event.ignore() event.ignore()
def dropEvent(self, event): def dropEvent(self, event):
if event.mimeData().hasUrls: drop_indicator = self.dropIndicatorPosition()
event.setDropAction(QtCore.Qt.CopyAction)
m = event.mimeData()
if m.hasUrls:
event.accept() event.accept()
for url in event.mimeData().urls():
for url in m.urls():
self.filename = str(url.toLocalFile()) self.filename = str(url.toLocalFile())
# file drop from outside application
if drop_indicator == QtWidgets.QAbstractItemView.OnItem:
if self.filename == "": if self.filename == "":
self.app.inform.emit("Open cancelled.") self.app.inform.emit("Open cancelled.")
else: else:
@ -94,9 +100,12 @@ class KeySensitiveListView(QtWidgets.QTreeView):
self.app.open_project(self.filename) self.app.open_project(self.filename)
else: else:
event.ignore() event.ignore()
else:
pass
else: else:
event.ignore() event.ignore()
class TreeItem: class TreeItem:
""" """
Item of a tree model Item of a tree model
@ -221,9 +230,15 @@ class ObjectCollection(QtCore.QAbstractItemModel):
### View ### View
self.view = KeySensitiveListView(app) self.view = KeySensitiveListView(app)
self.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.view.setModel(self) self.view.setModel(self)
self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
# self.view.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
# self.view.setDragEnabled(True)
# self.view.setAcceptDrops(True)
# self.view.setDropIndicatorShown(True)
font = QtGui.QFont() font = QtGui.QFont()
font.setPixelSize(12) font.setPixelSize(12)
font.setFamily("Seagoe UI") font.setFamily("Seagoe UI")
@ -273,6 +288,11 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_S: if key == QtCore.Qt.Key_S:
self.app.on_file_saveproject() self.app.on_file_saveproject()
# Toggle Plot Area
if key == QtCore.Qt.Key_F10:
self.app.on_toggle_plotarea()
return return
elif modifiers == QtCore.Qt.ShiftModifier: elif modifiers == QtCore.Qt.ShiftModifier:
@ -324,7 +344,20 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_Y: if key == QtCore.Qt.Key_Y:
self.app.on_skewy() self.app.on_skewy()
return return
elif modifiers == QtCore.Qt.AltModifier: elif modifiers == QtCore.Qt.AltModifier:
# Eanble all plots
if key == Qt.Key_1:
self.app.enable_all_plots()
# Disable all plots
if key == Qt.Key_2:
self.app.disable_all_plots()
# Disable all other plots
if key == Qt.Key_3:
self.app.disable_other_plots()
# 2-Sided PCB Tool # 2-Sided PCB Tool
if key == QtCore.Qt.Key_D: if key == QtCore.Qt.Key_D:
self.app.dblsidedtool.run() self.app.dblsidedtool.run()
@ -354,17 +387,17 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_F2: if key == QtCore.Qt.Key_F2:
webbrowser.open(self.app.video_url) webbrowser.open(self.app.video_url)
# Zoom Fit # Switch to Project Tab
if key == QtCore.Qt.Key_1: if key == QtCore.Qt.Key_1:
self.app.on_zoom_fit(None) self.app.on_select_tab('project')
# Zoom In # Switch to Selected Tab
if key == QtCore.Qt.Key_2: if key == QtCore.Qt.Key_2:
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse) self.app.on_select_tab('selected')
# Zoom Out # Switch to Tool Tab
if key == QtCore.Qt.Key_3: if key == QtCore.Qt.Key_3:
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse) self.app.on_select_tab('tool')
# Delete # Delete
if key == QtCore.Qt.Key_Delete and active: if key == QtCore.Qt.Key_Delete and active:
@ -444,6 +477,14 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if key == QtCore.Qt.Key_Y: if key == QtCore.Qt.Key_Y:
self.app.on_flipy() self.app.on_flipy()
# Zoom In
if key == QtCore.Qt.Key_Equal:
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], self.app.mouse)
# Zoom Out
if key == QtCore.Qt.Key_Minus:
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], self.app.mouse)
# Show shortcut list # Show shortcut list
if key == QtCore.Qt.Key_Ampersand: if key == QtCore.Qt.Key_Ampersand:
self.app.on_shortcut_list() self.app.on_shortcut_list()
@ -483,13 +524,13 @@ class ObjectCollection(QtCore.QAbstractItemModel):
if not self.hasIndex(row, column, parent): if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex() return QtCore.QModelIndex()
if not parent.isValid(): # if not parent.isValid():
parent_item = self.root_item # parent_item = self.root_item
else: # else:
parent_item = parent.internalPointer() # parent_item = parent.internalPointer()
parent_item = parent.internalPointer() if parent.isValid() else self.root_item
child_item = parent_item.child(row) child_item = parent_item.child(row)
if child_item: if child_item:
return self.createIndex(row, column, child_item) return self.createIndex(row, column, child_item)
else: else:
@ -573,35 +614,23 @@ class ObjectCollection(QtCore.QAbstractItemModel):
return True return True
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index): def flags(self, index):
default_flags = QtCore.QAbstractItemModel.flags(self, index)
if not index.isValid(): if not index.isValid():
return 0 return Qt.ItemIsEnabled | default_flags
# Prevent groups from selection # Prevent groups from selection
if not index.internalPointer().obj: if not index.internalPointer().obj:
return Qt.ItemIsEnabled return Qt.ItemIsEnabled
else: else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable | \
Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
return QtWidgets.QAbstractItemModel.flags(self, index) # return QtWidgets.QAbstractItemModel.flags(self, index)
# def data(self, index, role=Qt.Qt.DisplayRole):
# if not index.isValid() or not 0 <= index.row() < self.rowCount():
# return QtCore.QVariant()
# row = index.row()
# if role == Qt.Qt.DisplayRole:
# return self.object_list[row].options["name"]
# if role == Qt.Qt.DecorationRole:
# return self.icons[self.object_list[row].kind]
# # if role == Qt.Qt.CheckStateRole:
# # if row in self.checked_indexes:
# # return Qt.Qt.Checked
# # else:
# # return Qt.Qt.Unchecked
def print_list(self):
for obj in self.get_list():
print(obj)
def append(self, obj, active=False): def append(self, obj, active=False):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()") FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
@ -611,8 +640,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
# Check promises and clear if exists # Check promises and clear if exists
if name in self.promises: if name in self.promises:
self.promises.remove(name) self.promises.remove(name)
FlatCAMApp.App.log.debug("Promised object %s became available." % name) # FlatCAMApp.App.log.debug("Promised object %s became available." % name)
FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises)) # FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises))
# Prevent same name # Prevent same name
while name in self.get_names(): while name in self.get_names():
## Create a new name ## Create a new name

View File

@ -556,15 +556,38 @@ class ExcellonObjectUI(ObjectUI):
self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry]) self.ois_dwell = OptionalInputSection(self.dwell_cb, [self.dwelltime_entry])
# postprocessor selection # postprocessor selection
pp_excellon_label = QtWidgets.QLabel("Postprocessor") pp_excellon_label = QtWidgets.QLabel("Postprocessor:")
pp_excellon_label.setToolTip( pp_excellon_label.setToolTip(
"The json file that dictates\n" "The json file that dictates\n"
"gcode output." "gcode output."
) )
self.tools_box.addWidget(pp_excellon_label)
self.pp_excellon_name_cb = FCComboBox() self.pp_excellon_name_cb = FCComboBox()
self.pp_excellon_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus) self.pp_excellon_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus)
self.tools_box.addWidget(self.pp_excellon_name_cb) grid1.addWidget(pp_excellon_label, 10, 0)
grid1.addWidget(self.pp_excellon_name_cb, 10, 1)
# Probe depth
self.pdepth_label = QtWidgets.QLabel("Probe Z depth:")
self.pdepth_label.setToolTip(
"The maximum depth that the probe is allowed\n"
"to probe. Negative value, in current units."
)
grid1.addWidget(self.pdepth_label, 11, 0)
self.pdepth_entry = FCEntry()
grid1.addWidget(self.pdepth_entry, 11, 1)
self.pdepth_label.hide()
self.pdepth_entry.setVisible(False)
# Probe feedrate
self.feedrate_probe_label = QtWidgets.QLabel("Feedrate Probe:")
self.feedrate_probe_label.setToolTip(
"The feedrate used while the probe is probing."
)
grid1.addWidget(self.feedrate_probe_label, 12, 0)
self.feedrate_probe_entry = FCEntry()
grid1.addWidget(self.feedrate_probe_entry, 12, 1)
self.feedrate_probe_label.hide()
self.feedrate_probe_entry.setVisible(False)
choose_tools_label = QtWidgets.QLabel( choose_tools_label = QtWidgets.QLabel(
"Select from the Tools Table above\n" "Select from the Tools Table above\n"
@ -708,6 +731,8 @@ class GeometryObjectUI(ObjectUI):
self.geo_tools_table.setColumnWidth(0, 20) self.geo_tools_table.setColumnWidth(0, 20)
self.geo_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Offset', 'Type', 'TT', '', 'P']) self.geo_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Offset', 'Type', 'TT', '', 'P'])
self.geo_tools_table.setColumnHidden(5, True) self.geo_tools_table.setColumnHidden(5, True)
# stylesheet = "::section{Background-color:rgb(239,239,245)}"
# self.geo_tools_table.horizontalHeader().setStyleSheet(stylesheet)
self.geo_tools_table.horizontalHeaderItem(0).setToolTip( self.geo_tools_table.horizontalHeaderItem(0).setToolTip(
"This is the Tool Number.\n" "This is the Tool Number.\n"
@ -758,7 +783,7 @@ class GeometryObjectUI(ObjectUI):
"cut and negative for 'inside' cut." "cut and negative for 'inside' cut."
) )
self.grid1.addWidget(self.tool_offset_lbl, 0, 0) self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
self.tool_offset_entry = FloatEntry() self.tool_offset_entry = FCEntry()
spacer_lbl = QtWidgets.QLabel(" ") spacer_lbl = QtWidgets.QLabel(" ")
spacer_lbl.setFixedWidth(80) spacer_lbl.setFixedWidth(80)
@ -777,7 +802,7 @@ class GeometryObjectUI(ObjectUI):
self.addtool_entry_lbl.setToolTip( self.addtool_entry_lbl.setToolTip(
"Diameter for the new tool" "Diameter for the new tool"
) )
self.addtool_entry = FloatEntry() self.addtool_entry = FCEntry()
# hlay.addWidget(self.addtool_label) # hlay.addWidget(self.addtool_label)
# hlay.addStretch() # hlay.addStretch()
@ -1004,11 +1029,34 @@ class GeometryObjectUI(ObjectUI):
self.pp_geometry_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus) self.pp_geometry_name_cb.setFocusPolicy(QtCore.Qt.StrongFocus)
self.grid3.addWidget(self.pp_geometry_name_cb, 16, 1) self.grid3.addWidget(self.pp_geometry_name_cb, 16, 1)
# Probe depth
self.pdepth_label = QtWidgets.QLabel("Probe Z depth:")
self.pdepth_label.setToolTip(
"The maximum depth that the probe is allowed\n"
"to probe. Negative value, in current units."
)
self.grid3.addWidget(self.pdepth_label, 17, 0)
self.pdepth_entry = FCEntry()
self.grid3.addWidget(self.pdepth_entry, 17, 1)
self.pdepth_label.hide()
self.pdepth_entry.setVisible(False)
# Probe feedrate
self.feedrate_probe_label = QtWidgets.QLabel("Feedrate Probe:")
self.feedrate_probe_label.setToolTip(
"The feedrate used while the probe is probing."
)
self.grid3.addWidget(self.feedrate_probe_label, 18, 0)
self.feedrate_probe_entry = FCEntry()
self.grid3.addWidget(self.feedrate_probe_entry, 18, 1)
self.feedrate_probe_label.hide()
self.feedrate_probe_entry.setVisible(False)
warning_lbl = QtWidgets.QLabel( warning_lbl = QtWidgets.QLabel(
"Add at least one tool in the tool-table.\n" "Add at least one tool in the tool-table.\n"
"Click the header to select all, or Ctrl + LMB\n" "Click the header to select all, or Ctrl + LMB\n"
"for custom selection of tools.") "for custom selection of tools.")
self.grid3.addWidget(warning_lbl, 17, 0, 1, 2) self.grid3.addWidget(warning_lbl, 19, 0, 1, 2)
# Button # Button
self.generate_cnc_button = QtWidgets.QPushButton('Generate') self.generate_cnc_button = QtWidgets.QPushButton('Generate')
@ -1067,15 +1115,26 @@ class CNCObjectUI(ObjectUI):
self.plot_options_label = QtWidgets.QLabel("<b>Plot Options:</b>") self.plot_options_label = QtWidgets.QLabel("<b>Plot Options:</b>")
self.custom_box.addWidget(self.plot_options_label) self.custom_box.addWidget(self.plot_options_label)
# # Tool dia for plot self.cncplot_method_label = QtWidgets.QLabel("Plot kind:")
# tdlabel = QtWidgets.QLabel('Tool dia:') self.cncplot_method_label.setToolTip(
# tdlabel.setToolTip( "This selects the kind of geometries on the canvas to plot.\n"
# "Diameter of the tool to be\n" "Those can be either of type 'Travel' which means the moves\n"
# "rendered in the plot." "above the work piece or it can be of type 'Cut',\n"
# ) "which means the moves that cut into the material."
# grid0.addWidget(tdlabel, 1, 0) )
# self.tooldia_entry = LengthEntry()
# grid0.addWidget(self.tooldia_entry, 1, 1) self.cncplot_method_combo = RadioSet([
{"label": "All", "value": "all"},
{"label": "Travel", "value": "travel"},
{"label": "Cut", "value": "cut"}
], stretch=False)
f_lay = QtWidgets.QFormLayout()
self.custom_box.addLayout(f_lay)
f_lay.addRow(self.cncplot_method_label, self.cncplot_method_combo)
e1_lbl = QtWidgets.QLabel('')
self.custom_box.addWidget(e1_lbl)
hlay = QtWidgets.QHBoxLayout() hlay = QtWidgets.QHBoxLayout()
self.custom_box.addLayout(hlay) self.custom_box.addLayout(hlay)
@ -1115,6 +1174,8 @@ class CNCObjectUI(ObjectUI):
self.cnc_tools_table.setColumnWidth(0, 20) self.cnc_tools_table.setColumnWidth(0, 20)
self.cnc_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Offset', 'Type', 'TT', '', 'P']) self.cnc_tools_table.setHorizontalHeaderLabels(['#', 'Dia', 'Offset', 'Type', 'TT', '', 'P'])
self.cnc_tools_table.setColumnHidden(5, True) self.cnc_tools_table.setColumnHidden(5, True)
# stylesheet = "::section{Background-color:rgb(239,239,245)}"
# self.cnc_tools_table.horizontalHeader().setStyleSheet(stylesheet)
# Update plot button # Update plot button
self.updateplot_button = QtWidgets.QPushButton('Update Plot') self.updateplot_button = QtWidgets.QPushButton('Update Plot')

View File

@ -287,8 +287,8 @@ class ParseFont():
elif font_type == 'regular': elif font_type == 'regular':
path_filename = regular_dict[font_name] path_filename = regular_dict[font_name]
except Exception as e: except Exception as e:
self.app.inform.emit("[error_notcl] Font not supported, try another one.") self.app.inform.emit("[ERROR_NOTCL] Font not supported, try another one.")
log.debug("[error_notcl] Font Loading: %s" % str(e)) log.debug("[ERROR_NOTCL] Font Loading: %s" % str(e))
return "flatcam font parse failed" return "flatcam font parse failed"
face = ft.Face(path_filename) face = ft.Face(path_filename)

View File

@ -121,7 +121,7 @@ def path2shapely(path, object_type, res=1.0):
# geo_element = Polygon(points) # geo_element = Polygon(points)
geo_element = LineString(points) geo_element = LineString(points)
else: else:
log.error("[error]: Not a valid target object.") log.error("[ERROR]: Not a valid target object.")
if not points: if not points:
continue continue
else: else:
@ -639,7 +639,7 @@ def parse_svg_transform(trstr):
continue continue
# raise Exception("Don't know how to parse: %s" % trstr) # raise Exception("Don't know how to parse: %s" % trstr)
log.error("[error] Don't know how to parse: %s" % trstr) log.error("[ERROR] Don't know how to parse: %s" % trstr)
return trlist return trlist

View File

@ -9,9 +9,93 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
6.02.2019
- fixed the units calculators crash FlatCAM when using comma as decimal separator
5.02.3019
- added a text in the Selected Tab which is showed whenever the Selected Tab is selected but without having an object selected to display it's properties
- added an initial text in the Tools tab
- added possibility to use the shortcut key for shortcut list in the Notebook tabs
- added a way to set the Probe depth if Toolchange_Probe postprocessors are selected
- finished the postprocessor file for MACH3 tool probing on toolchange event
- added a new parameter to set the feedrate of the probing in case the used postprocessor does probing (has toolchange_probe in it's name)
- fixed bug in Marlin postprocessor for the Excellon files; the header and toolchange event always used the parenthesis witch is not compatible with GCode for Marlin
- fixed a issue with a move to Z_move before any toolchange
4.02.2019
- modified the Toolchange_Probe_general postprocessor file to remove any Z moves before the actual toolchange event
- created a prototype postprocessor file for usage with tool probing in MACH3
- added the default values for Tool Film and Tool Panelize to the Edit -> Preferences
- added a new parameter in the Tool Film which control the thickness of the stroke width in the resulting SVG. It's a scale parameter.
- whatever was the visibility of the corresponding toolbar when we enter in the Editor, it will be set after exit from the Editor (either Geometry Editor or Excellon Editor).
- added ability to be detached for the tabs in the Notebook section (Project, Selected and Tool)
- added ability for all detachable tabs to be restored to the same position from where they were detached.
- changed the shortcut keys for Zoom In, Zoom Out and Zoom Fit from 1, 2, 3 to '-', '=' respectively 'V'. Added new shortcut keys '1', '2', '3' for Select Project Tab, Select Selected Tab and Select Tool Tab.
- formatted the Shortcut List Tab into a HTML table
3.3.2019
- updated the new shortcut list with the shortcuts added lately
- now the special messages in the Shell are color coded according to the level. Before they all were RED. Now the WARNINGS are yellow, ERRORS are red and SUCCESS is a dark green. Also the level is in CAPS LOCK to make them more obvious
- some more changes to GUI interface (solved issues)
- added some status bar messages in the Geometry Editor to guide the user when using the Geometry Tools
- now the '`' shortcut key that shows the 'shortcut key list' in Editors points to the same window which is created in a tab no longer as a pop-up window. This tab can be detached if needed.
- added a remove_tools() function before install_tools() in the init_tools() that is called when creating a new project. Should solve the issue with having double menu entry's in the TOOLS menu
- fixed remove_tools() so the Tcl Shell action is readded to the Tools menu and reconnected to it's slot function
- added an automatic name on each save operation based on the object name and/or the current date
- added more information's for the statistics
2.2.2019
- code cleanup in Tools
- some GUI structure optimization's
- added protection against entering float numbers with comma separator instead of decimal dot separator in key points of FlatCAM (not everywhere)
- added a choice of plotting the kind of geometry for the CNC plot (all, travel and cut kind of geometries) in CNCJob Selected Tab
- added a new postprocessor file named: 'probe_from_zmove' which allow probing to be done from z_move position on toolchange event
- fixed the snap magnet button in Geometry Editor, restored the checkable property to True
- some more changes in the Editors GUI in deactivate() function
- a fix for saving as empty an edited new and empty Excellon Object
1.02.2019
- fixed postprocessor files so now the bounds values are right aligned (assuming max string length of 9 chars which means 4 digits and 4 decimals)
- corrected small type in list_sys Tcl command; added a protection of the Plot Area Tab after a successful edit.
- remade the way FlatCAM saves the GUI position data from a file (previously) to use PyQt QSettings
- added a 'theme' combo selection in Edit -> Preferences. Two themes are available: standard and compact.
- some code cleanup
- fixed a source of possible errors in DetachableTab Widget.
- fixed gcode conversion/scale (on units change) when multiple values are found on each line
- replaced the pop-up window for the shortcut list with a new detachable tab
- removed the pop-up messages from the rotate, skew, flip commands
31.01.2019
- added a parameter ('Fast plunge' in Edit -> Preferences -> Geometry Options and Excellon Options) to control if the fast move to Z_move is done or not
- added new function to toggle fullscreen status in Menu -> View -> Toggle Full Screen. Shortcut key: Alt+F10
- added key shortcuts for Enable Plots, Disable Plots and Disable other plots functions (Alt+1, Alt+2, Alt+3)
- hidden the snap magnet entry and snap magnet toggle from the main view; they are now active only in Editor Mode
- updated the camlib.CNCJob.scale() function so now the GCode is scaled also (quite a HACK :( it will need to be replaced at some point)). Units change work now on the GCODE also.
- added the bounds coordinates to the GCODE header
- FlatCAM saves now to a file in self.data_path the toolbar positions and the position of TCL Shell
- Plot Area Tab view can now be toggled, added entry in View Menu and shortcut key CTRL+F10
- All the tabs in the GUI right side are (Plot Are, Preferences etc) are now detachable to a separate windows which when closed it returns in the previous location in the toolbar. Those detached tabs can be also reattached by drag and drop.
30.01.2019 30.01.2019
- added a space before Y coordinate in end_code() function in some of the postprocessor files - added a space before Y coordinate in end_code() function in some of the postprocessor files
- added in Calculators Tool an Electroplating Calculator.
- remade the App Menu for Editors: now they will be showed only when the respective Editor is active and hidden when the Editor is closed.
- added a traceback report in the TCL Shell for the errors that don't allow creation of an object; useful to trace exceptions/errors
- in case that the Toolchange X,Y parameter in Selected (or in Preferences) are deleted then the app will still do the job using the current coordinates for toolchange
- fixed an issue in camlib.CNCJob where tha variable self.toolchange_xy was used for 2 different purposes which created loss of information.
- fixed unit conversion functions in case the toolchange_xy parameter is None
- more fixes in camlib.CNCJob regarding usage of toolchange (in case it is None)
- fixed postprocessor files to work with toolchange_xy parameter value = None (no values in Edit - Preferences fields)
- fixed Tcl commands CncJob and DrillCncJob to work with toolchange
- added to the postprocessor files the command after toolchange to go with G00 (fastest) to "Z Move" value of Z pozition.
29.01.2019 29.01.2019
@ -243,7 +327,7 @@ CAD program, and create G-Code for Isolation routing.
- solved a small bug that didn't allow the Paint Job to be done with lines when the results were geometries not iterable - solved a small bug that didn't allow the Paint Job to be done with lines when the results were geometries not iterable
- added protection for the case when trying to run the cncjob Tcl Command on a Geometry object that do not have solid geometry or one that is multi-tool - added protection for the case when trying to run the cncjob Tcl Command on a Geometry object that do not have solid geometry or one that is multi-tool
- Paint Tool Table: now it is possible to edit a tool to a new diameter and then edit another tool to the former diameter of the first edited tool - Paint Tool Table: now it is possible to edit a tool to a new diameter and then edit another tool to the former diameter of the first edited tool
- added a new type of warning, [warning_notcl] - added a new type of warning, [WARNING_NOTCL]
- fixed conflict with "space" keyboard shortcut for CNC job - fixed conflict with "space" keyboard shortcut for CNC job
16.12.2018 16.12.2018

362
camlib.py
View File

@ -186,7 +186,7 @@ class Geometry(object):
if isinstance(self.solid_geometry, list): if isinstance(self.solid_geometry, list):
return len(self.solid_geometry) == 0 return len(self.solid_geometry) == 0
self.app.inform.emit("[error_notcl] self.solid_geometry is neither BaseGeometry or list.") self.app.inform.emit("[ERROR_NOTCL] self.solid_geometry is neither BaseGeometry or list.")
return return
def subtract_polygon(self, points): def subtract_polygon(self, points):
@ -300,7 +300,7 @@ class Geometry(object):
# else: # else:
# return self.solid_geometry.bounds # return self.solid_geometry.bounds
# except Exception as e: # except Exception as e:
# self.app.inform.emit("[error_notcl] Error cause: %s" % str(e)) # self.app.inform.emit("[ERROR_NOTCL] Error cause: %s" % str(e))
# log.debug("Geometry->bounds()") # log.debug("Geometry->bounds()")
# if self.solid_geometry is None: # if self.solid_geometry is None:
@ -1361,7 +1361,7 @@ class Geometry(object):
self.solid_geometry = mirror_geom(self.solid_geometry) self.solid_geometry = mirror_geom(self.solid_geometry)
self.app.inform.emit('[success]Object was mirrored ...') self.app.inform.emit('[success]Object was mirrored ...')
except AttributeError: except AttributeError:
self.app.inform.emit("[error_notcl] Failed to mirror. No object selected") self.app.inform.emit("[ERROR_NOTCL] Failed to mirror. No object selected")
@ -1401,7 +1401,7 @@ class Geometry(object):
self.solid_geometry = rotate_geom(self.solid_geometry) self.solid_geometry = rotate_geom(self.solid_geometry)
self.app.inform.emit('[success]Object was rotated ...') self.app.inform.emit('[success]Object was rotated ...')
except AttributeError: except AttributeError:
self.app.inform.emit("[error_notcl] Failed to rotate. No object selected") self.app.inform.emit("[ERROR_NOTCL] Failed to rotate. No object selected")
def skew(self, angle_x, angle_y, point): def skew(self, angle_x, angle_y, point):
""" """
@ -1437,7 +1437,7 @@ class Geometry(object):
self.solid_geometry = skew_geom(self.solid_geometry) self.solid_geometry = skew_geom(self.solid_geometry)
self.app.inform.emit('[success]Object was skewed ...') self.app.inform.emit('[success]Object was skewed ...')
except AttributeError: except AttributeError:
self.app.inform.emit("[error_notcl] Failed to skew. No object selected") self.app.inform.emit("[ERROR_NOTCL] Failed to skew. No object selected")
# if type(self.solid_geometry) == list: # if type(self.solid_geometry) == list:
# self.solid_geometry = [affinity.skew(g, angle_x, angle_y, origin=(px, py)) # self.solid_geometry = [affinity.skew(g, angle_x, angle_y, origin=(px, py))
@ -2454,9 +2454,11 @@ class Gerber (Geometry):
region = Polygon() region = Polygon()
else: else:
region = Polygon(path) region = Polygon(path)
if not region.is_valid: if not region.is_valid:
if not follow: 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: if not region.is_empty:
poly_buffer.append(region) poly_buffer.append(region)
@ -2531,8 +2533,8 @@ class Gerber (Geometry):
pass pass
last_path_aperture = current_aperture last_path_aperture = current_aperture
else: else:
self.app.inform.emit("[warning] Coordinates missing, line ignored: %s" % str(gline)) self.app.inform.emit("[WARNING] Coordinates missing, line ignored: %s" % str(gline))
self.app.inform.emit("[warning_notcl] GERBER file might be CORRUPT. Check the file !!!") self.app.inform.emit("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!")
elif current_operation_code == 2: elif current_operation_code == 2:
if len(path) > 1: if len(path) > 1:
@ -2550,7 +2552,7 @@ class Gerber (Geometry):
geo = Polygon(path) geo = Polygon(path)
except ValueError: except ValueError:
log.warning("Problem %s %s" % (gline, line_num)) log.warning("Problem %s %s" % (gline, line_num))
self.app.inform.emit("[error] Region does not have enough points. " self.app.inform.emit("[ERROR] Region does not have enough points. "
"File will be processed but there are parser errors. " "File will be processed but there are parser errors. "
"Line number: %s" % str(line_num)) "Line number: %s" % str(line_num))
else: else:
@ -2574,8 +2576,8 @@ class Gerber (Geometry):
if linear_x is not None and linear_y is not None: if linear_x is not None and linear_y is not None:
path = [[linear_x, linear_y]] # Start new path path = [[linear_x, linear_y]] # Start new path
else: else:
self.app.inform.emit("[warning] Coordinates missing, line ignored: %s" % str(gline)) self.app.inform.emit("[WARNING] Coordinates missing, line ignored: %s" % str(gline))
self.app.inform.emit("[warning_notcl] GERBER file might be CORRUPT. Check the file !!!") self.app.inform.emit("[WARNING_NOTCL] GERBER file might be CORRUPT. Check the file !!!")
# Flash # Flash
# Not allowed in region mode. # Not allowed in region mode.
@ -2838,6 +2840,7 @@ class Gerber (Geometry):
if self.use_buffer_for_union: if self.use_buffer_for_union:
log.debug("Union by buffer...") log.debug("Union by buffer...")
new_poly = MultiPolygon(poly_buffer) new_poly = MultiPolygon(poly_buffer)
new_poly = new_poly.buffer(0.00000001) new_poly = new_poly.buffer(0.00000001)
new_poly = new_poly.buffer(-0.00000001) new_poly = new_poly.buffer(-0.00000001)
@ -2857,8 +2860,9 @@ class Gerber (Geometry):
traceback.print_tb(tb) traceback.print_tb(tb)
#print traceback.format_exc() #print traceback.format_exc()
log.error("PARSING FAILED. Line %d: %s" % (line_num, gline)) log.error("Gerber PARSING FAILED. Line %d: %s" % (line_num, gline))
self.app.inform.emit("[error] Gerber Parser ERROR.\n Line %d: %s" % (line_num, gline), repr(err)) loc = 'Gerber Line #%d Gerber Line Content: %s\n' % (line_num, gline) + repr(err)
self.app.inform.emit("[ERROR]Gerber Parser ERROR.\n%s:" % loc)
@staticmethod @staticmethod
def create_flash_geometry(location, aperture, steps_per_circle=None): def create_flash_geometry(location, aperture, steps_per_circle=None):
@ -3035,7 +3039,7 @@ class Gerber (Geometry):
try: try:
xfactor = float(xfactor) xfactor = float(xfactor)
except: except:
self.app.inform.emit("[error_notcl] Scale factor has to be a number: integer or float.") self.app.inform.emit("[ERROR_NOTCL] Scale factor has to be a number: integer or float.")
return return
if yfactor is None: if yfactor is None:
@ -3044,7 +3048,7 @@ class Gerber (Geometry):
try: try:
yfactor = float(yfactor) yfactor = float(yfactor)
except: except:
self.app.inform.emit("[error_notcl] Scale factor has to be a number: integer or float.") self.app.inform.emit("[ERROR_NOTCL] Scale factor has to be a number: integer or float.")
return return
if point is None: if point is None:
@ -3096,7 +3100,7 @@ class Gerber (Geometry):
try: try:
dx, dy = vect dx, dy = vect
except TypeError: except TypeError:
self.app.inform.emit("[error_notcl]An (x,y) pair of values are needed. " self.app.inform.emit("[ERROR_NOTCL]An (x,y) pair of values are needed. "
"Probable you entered only one value in the Offset field.") "Probable you entered only one value in the Offset field.")
return return
@ -3460,7 +3464,7 @@ class Excellon(Geometry):
# and we need to exit from here # and we need to exit from here
if self.detect_gcode_re.search(eline): if self.detect_gcode_re.search(eline):
log.warning("This is GCODE mark: %s" % eline) log.warning("This is GCODE mark: %s" % eline)
self.app.inform.emit('[error_notcl] This is GCODE mark: %s' % eline) self.app.inform.emit('[ERROR_NOTCL] This is GCODE mark: %s' % eline)
return return
# Header Begin (M48) # # Header Begin (M48) #
@ -3987,10 +3991,13 @@ class Excellon(Geometry):
# from self.defaults['excellon_units'] # from self.defaults['excellon_units']
log.info("Zeros: %s, Units %s." % (self.zeros, self.units)) log.info("Zeros: %s, Units %s." % (self.zeros, self.units))
except Exception as e: except Exception as e:
log.error("PARSING FAILED. Line %d: %s" % (line_num, eline)) log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline))
self.app.inform.emit('[error] Excellon Parser ERROR.\nPARSING FAILED. Line %d: %s' % (line_num, eline)) msg = "[ERROR_NOTCL] An internal error has ocurred. See shell.\n"
msg += '[ERROR] Excellon Parser error.\nParsing Failed. Line %d: %s\n' % (line_num, eline)
msg += traceback.format_exc()
self.app.inform.emit(msg)
return "fail" return "fail"
def parse_number(self, number_str): def parse_number(self, number_str):
@ -4059,7 +4066,7 @@ class Excellon(Geometry):
for drill in self.drills: for drill in self.drills:
# poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0) # poly = drill['point'].buffer(self.tools[drill['tool']]["C"]/2.0)
if drill['tool'] is '': if drill['tool'] is '':
self.app.inform.emit("[warning] Excellon.create_geometry() -> a drill location was skipped " self.app.inform.emit("[WARNING] Excellon.create_geometry() -> a drill location was skipped "
"due of not having a tool associated.\n" "due of not having a tool associated.\n"
"Check the resulting GCode.") "Check the resulting GCode.")
log.debug("Excellon.create_geometry() -> a drill location was skipped " log.debug("Excellon.create_geometry() -> a drill location was skipped "
@ -4363,11 +4370,11 @@ class CNCjob(Geometry):
def __init__(self, def __init__(self,
units="in", kind="generic", tooldia=0.0, units="in", kind="generic", tooldia=0.0,
z_cut=-0.002, z_move=0.1, z_cut=-0.002, z_move=0.1,
feedrate=3.0, feedrate_z=3.0, feedrate_rapid=3.0, feedrate=3.0, feedrate_z=3.0, feedrate_rapid=3.0, feedrate_probe=3.0,
pp_geometry_name='default', pp_excellon_name='default', pp_geometry_name='default', pp_excellon_name='default',
depthpercut = 0.1, depthpercut=0.1,z_pdepth=-0.02,
spindlespeed=None, dwell=True, dwelltime=1000, spindlespeed=None, dwell=True, dwelltime=1000,
toolchangez=0.787402, toolchangez=0.787402, toolchange_xy=[0.0, 0.0],
endz=2.0, endz=2.0,
segx=None, segx=None,
segy=None, segy=None,
@ -4392,7 +4399,8 @@ class CNCjob(Geometry):
self.tooldia = tooldia self.tooldia = tooldia
self.toolchangez = toolchangez self.toolchangez = toolchangez
self.toolchange_xy = None self.toolchange_xy = toolchange_xy
self.toolchange_xy_type = None
self.endz = endz self.endz = endz
self.depthpercut = depthpercut self.depthpercut = depthpercut
@ -4411,6 +4419,15 @@ class CNCjob(Geometry):
self.pp_excellon_name = pp_excellon_name self.pp_excellon_name = pp_excellon_name
self.pp_excellon = self.app.postprocessors[self.pp_excellon_name] self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
# Controls if the move from Z_Toolchange to Z_Move is done fast with G0 or normally with G1
self.f_plunge = None
# how much depth the probe can probe before error
self.z_pdepth = z_pdepth if z_pdepth else None
# the feedrate(speed) with which the probel travel while probing
self.feedrate_probe = feedrate_probe if feedrate_probe else None
self.spindlespeed = spindlespeed self.spindlespeed = spindlespeed
self.dwell = dwell self.dwell = dwell
self.dwelltime = dwelltime self.dwelltime = dwelltime
@ -4420,6 +4437,9 @@ class CNCjob(Geometry):
self.input_geometry_bounds = None self.input_geometry_bounds = None
self.oldx = None
self.oldy = None
# Attributes to be included in serialization # Attributes to be included in serialization
# Always append to it because it carries contents # Always append to it because it carries contents
# from Geometry. # from Geometry.
@ -4490,7 +4510,7 @@ class CNCjob(Geometry):
return path return path
def generate_from_excellon_by_tool(self, exobj, tools="all", drillz = 3.0, def generate_from_excellon_by_tool(self, exobj, tools="all", drillz = 3.0,
toolchange=False, toolchangez=0.1, toolchangexy="0.0, 0.0", toolchange=False, toolchangez=0.1, toolchangexy='',
endz=2.0, startz=None, endz=2.0, startz=None,
excellon_optimization_type='B'): excellon_optimization_type='B'):
""" """
@ -4520,25 +4540,40 @@ class CNCjob(Geometry):
:rtype: None :rtype: None
""" """
if drillz > 0: if drillz > 0:
self.app.inform.emit("[warning] The Cut Z parameter has positive value. " self.app.inform.emit("[WARNING] The Cut Z parameter has positive value. "
"It is the depth value to drill into material.\n" "It is the depth value to drill into material.\n"
"The Cut Z parameter needs to have a negative value, assuming it is a typo " "The Cut Z parameter needs to have a negative value, assuming it is a typo "
"therefore the app will convert the value to negative. " "therefore the app will convert the value to negative. "
"Check the resulting CNC code (Gcode etc).") "Check the resulting CNC code (Gcode etc).")
self.z_cut = -drillz self.z_cut = -drillz
elif drillz == 0: elif drillz == 0:
self.app.inform.emit("[warning] The Cut Z parameter is zero. " self.app.inform.emit("[WARNING] The Cut Z parameter is zero. "
"There will be no cut, skipping %s file" % exobj.options['name']) "There will be no cut, skipping %s file" % exobj.options['name'])
return return
else: else:
self.z_cut = drillz self.z_cut = drillz
self.toolchangez = toolchangez self.toolchangez = toolchangez
try:
if toolchangexy == '':
self.toolchange_xy = None
else:
self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")] self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
if len(self.toolchange_xy) < 2:
self.app.inform.emit("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
"in the format (x, y) \nbut now there is only one value, not two. ")
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> %s" % str(e))
pass
self.startz = startz self.startz = startz
self.endz = endz self.endz = endz
self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
p = self.pp_excellon
log.debug("Creating CNC Job from Excellon...") log.debug("Creating CNC Job from Excellon...")
# Tools # Tools
@ -4576,15 +4611,19 @@ class CNCjob(Geometry):
self.gcode = [] self.gcode = []
# Basic G-Code macros self.f_plunge = self.app.defaults["excellon_f_plunge"]
self.pp_excellon = self.app.postprocessors[self.pp_excellon_name]
p = self.pp_excellon
# Initialization # Initialization
gcode = self.doformat(p.start_code) gcode = self.doformat(p.start_code)
gcode += self.doformat(p.feedrate_code) gcode += self.doformat(p.feedrate_code)
gcode += self.doformat(p.lift_code, x=0, y=0)
gcode += self.doformat(p.startz_code, x=0, y=0) if toolchange is False:
if self.toolchange_xy is not None:
gcode += self.doformat(p.lift_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
gcode += self.doformat(p.startz_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
else:
gcode += self.doformat(p.lift_code, x=0.0, y=0.0)
gcode += self.doformat(p.startz_code, x=0.0, y=0.0)
# Distance callback # Distance callback
class CreateDistanceCallback(object): class CreateDistanceCallback(object):
@ -4618,8 +4657,13 @@ class CNCjob(Geometry):
locations.append((point.coords.xy[0][0], point.coords.xy[1][0])) locations.append((point.coords.xy[0][0], point.coords.xy[1][0]))
return locations return locations
oldx = 0 if self.toolchange_xy is not None:
oldy = 0 self.oldx = self.toolchange_xy[0]
self.oldy = self.toolchange_xy[1]
else:
self.oldx = 0.0
self.oldy = 0.0
measured_distance = 0 measured_distance = 0
current_platform = platform.architecture()[0] current_platform = platform.architecture()[0]
@ -4684,7 +4728,7 @@ class CNCjob(Geometry):
if tool in points: if tool in points:
# Tool change sequence (optional) # Tool change sequence (optional)
if toolchange: if toolchange:
gcode += self.doformat(p.toolchange_code,toolchangexy=(oldx, oldy)) gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start gcode += self.doformat(p.spindle_code) # Spindle start
if self.dwell is True: if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time gcode += self.doformat(p.dwell_code) # Dwell time
@ -4702,9 +4746,9 @@ class CNCjob(Geometry):
gcode += self.doformat(p.down_code, x=locx, y=locy) gcode += self.doformat(p.down_code, x=locx, y=locy)
gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
gcode += self.doformat(p.lift_code, x=locx, y=locy) gcode += self.doformat(p.lift_code, x=locx, y=locy)
measured_distance += abs(distance_euclidian(locx, locy, oldx, oldy)) measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
oldx = locx self.oldx = locx
oldy = locy self.oldy = locy
log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance)) log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
elif excellon_optimization_type == 'B': elif excellon_optimization_type == 'B':
log.debug("Using OR-Tools Basic drill path optimization.") log.debug("Using OR-Tools Basic drill path optimization.")
@ -4758,7 +4802,7 @@ class CNCjob(Geometry):
if tool in points: if tool in points:
# Tool change sequence (optional) # Tool change sequence (optional)
if toolchange: if toolchange:
gcode += self.doformat(p.toolchange_code,toolchangexy=(oldx, oldy)) gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start) gcode += self.doformat(p.spindle_code) # Spindle start)
if self.dwell is True: if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time gcode += self.doformat(p.dwell_code) # Dwell time
@ -4775,12 +4819,12 @@ class CNCjob(Geometry):
gcode += self.doformat(p.down_code, x=locx, y=locy) gcode += self.doformat(p.down_code, x=locx, y=locy)
gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy) gcode += self.doformat(p.up_to_zero_code, x=locx, y=locy)
gcode += self.doformat(p.lift_code, x=locx, y=locy) gcode += self.doformat(p.lift_code, x=locx, y=locy)
measured_distance += abs(distance_euclidian(locx, locy, oldx, oldy)) measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
oldx = locx self.oldx = locx
oldy = locy self.oldy = locy
log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance)) log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
else: else:
self.app.inform.emit("[error_notcl] Wrong optimization type selected.") self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.")
return return
else: else:
log.debug("Using Travelling Salesman drill path optimization.") log.debug("Using Travelling Salesman drill path optimization.")
@ -4792,7 +4836,7 @@ class CNCjob(Geometry):
if tool in points: if tool in points:
# Tool change sequence (optional) # Tool change sequence (optional)
if toolchange: if toolchange:
gcode += self.doformat(p.toolchange_code, toolchangexy=(oldx, oldy)) gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start) gcode += self.doformat(p.spindle_code) # Spindle start)
if self.dwell is True: if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time gcode += self.doformat(p.dwell_code) # Dwell time
@ -4811,15 +4855,15 @@ class CNCjob(Geometry):
gcode += self.doformat(p.down_code, x=point[0], y=point[1]) gcode += self.doformat(p.down_code, x=point[0], y=point[1])
gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1]) gcode += self.doformat(p.up_to_zero_code, x=point[0], y=point[1])
gcode += self.doformat(p.lift_code, x=point[0], y=point[1]) gcode += self.doformat(p.lift_code, x=point[0], y=point[1])
measured_distance += abs(distance_euclidian(point[0], point[1], oldx, oldy)) measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy))
oldx = point[0] self.oldx = point[0]
oldy = point[1] self.oldy = point[1]
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance)) log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
gcode += self.doformat(p.spindle_stop_code) # Spindle stop gcode += self.doformat(p.spindle_stop_code) # Spindle stop
gcode += self.doformat(p.end_code, x=0, y=0) gcode += self.doformat(p.end_code, x=0, y=0)
measured_distance += abs(distance_euclidian(oldx, oldy, 0, 0)) measured_distance += abs(distance_euclidian(self.oldx, self.oldy, 0, 0))
log.debug("The total travel distance including travel to end position is: %s" % log.debug("The total travel distance including travel to end position is: %s" %
str(measured_distance) + '\n') str(measured_distance) + '\n')
self.gcode = gcode self.gcode = gcode
@ -4887,19 +4931,32 @@ class CNCjob(Geometry):
self.multidepth = multidepth self.multidepth = multidepth
self.toolchangez = toolchangez self.toolchangez = toolchangez
try:
if toolchangexy == '':
self.toolchange_xy = None
else:
self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")] self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
if len(self.toolchange_xy) < 2:
self.app.inform.emit("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
"in the format (x, y) \nbut now there is only one value, not two. ")
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.generate_from_multitool_geometry() --> %s" % str(e))
pass
self.pp_geometry_name = pp_geometry_name if pp_geometry_name else 'default' self.pp_geometry_name = pp_geometry_name if pp_geometry_name else 'default'
self.f_plunge = self.app.defaults["geometry_f_plunge"]
if self.z_cut > 0: if self.z_cut > 0:
self.app.inform.emit("[warning] The Cut Z parameter has positive value. " self.app.inform.emit("[WARNING] The Cut Z parameter has positive value. "
"It is the depth value to cut into material.\n" "It is the depth value to cut into material.\n"
"The Cut Z parameter needs to have a negative value, assuming it is a typo " "The Cut Z parameter needs to have a negative value, assuming it is a typo "
"therefore the app will convert the value to negative." "therefore the app will convert the value to negative."
"Check the resulting CNC code (Gcode etc).") "Check the resulting CNC code (Gcode etc).")
self.z_cut = -self.z_cut self.z_cut = -self.z_cut
elif self.z_cut == 0: elif self.z_cut == 0:
self.app.inform.emit("[warning] The Cut Z parameter is zero. " self.app.inform.emit("[WARNING] The Cut Z parameter is zero. "
"There will be no cut, skipping %s file" % self.options['name']) "There will be no cut, skipping %s file" % self.options['name'])
## Index first and last points in paths ## Index first and last points in paths
@ -4936,13 +4993,16 @@ class CNCjob(Geometry):
self.gcode = self.doformat(p.start_code) self.gcode = self.doformat(p.start_code)
self.gcode += self.doformat(p.feedrate_code) # sets the feed rate self.gcode += self.doformat(p.feedrate_code) # sets the feed rate
if toolchange is False:
self.gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height self.gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height
self.gcode += self.doformat(p.startz_code, x=0, y=0) self.gcode += self.doformat(p.startz_code, x=0, y=0)
if toolchange: if toolchange:
if "line_xyz" in self.pp_geometry_name: # if "line_xyz" in self.pp_geometry_name:
self.gcode += self.doformat(p.toolchange_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1]) # self.gcode += self.doformat(p.toolchange_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
else: # else:
# self.gcode += self.doformat(p.toolchange_code)
self.gcode += self.doformat(p.toolchange_code) self.gcode += self.doformat(p.toolchange_code)
self.gcode += self.doformat(p.spindle_code) # Spindle start self.gcode += self.doformat(p.spindle_code) # Spindle start
@ -5023,13 +5083,13 @@ class CNCjob(Geometry):
""" """
if not isinstance(geometry, Geometry): if not isinstance(geometry, Geometry):
self.app.inform.emit("[error]Expected a Geometry, got %s" % type(geometry)) self.app.inform.emit("[ERROR]Expected a Geometry, got %s" % type(geometry))
return 'fail' return 'fail'
log.debug("Generate_from_geometry_2()") log.debug("Generate_from_geometry_2()")
# if solid_geometry is empty raise an exception # if solid_geometry is empty raise an exception
if not geometry.solid_geometry: if not geometry.solid_geometry:
self.app.inform.emit("[error_notcl]Trying to generate a CNC Job " self.app.inform.emit("[ERROR_NOTCL]Trying to generate a CNC Job "
"from a Geometry object without solid_geometry.") "from a Geometry object without solid_geometry.")
temp_solid_geometry = [] temp_solid_geometry = []
@ -5067,19 +5127,32 @@ class CNCjob(Geometry):
self.multidepth = multidepth self.multidepth = multidepth
self.toolchangez = toolchangez self.toolchangez = toolchangez
try:
if toolchangexy == '':
self.toolchange_xy = None
else:
self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")] self.toolchange_xy = [float(eval(a)) for a in toolchangexy.split(",")]
if len(self.toolchange_xy) < 2:
self.app.inform.emit("[ERROR]The Toolchange X,Y field in Edit -> Preferences has to be "
"in the format (x, y) \nbut now there is only one value, not two. ")
return 'fail'
except Exception as e:
log.debug("camlib.CNCJob.generate_from_geometry_2() --> %s" % str(e))
pass
self.pp_geometry_name = pp_geometry_name if pp_geometry_name else 'default' self.pp_geometry_name = pp_geometry_name if pp_geometry_name else 'default'
self.f_plunge = self.app.defaults["geometry_f_plunge"]
if self.z_cut > 0: if self.z_cut > 0:
self.app.inform.emit("[warning] The Cut Z parameter has positive value. " self.app.inform.emit("[WARNING] The Cut Z parameter has positive value. "
"It is the depth value to cut into material.\n" "It is the depth value to cut into material.\n"
"The Cut Z parameter needs to have a negative value, assuming it is a typo " "The Cut Z parameter needs to have a negative value, assuming it is a typo "
"therefore the app will convert the value to negative." "therefore the app will convert the value to negative."
"Check the resulting CNC code (Gcode etc).") "Check the resulting CNC code (Gcode etc).")
self.z_cut = -self.z_cut self.z_cut = -self.z_cut
elif self.z_cut == 0: elif self.z_cut == 0:
self.app.inform.emit("[warning] The Cut Z parameter is zero. " self.app.inform.emit("[WARNING] The Cut Z parameter is zero. "
"There will be no cut, skipping %s file" % geometry.options['name']) "There will be no cut, skipping %s file" % geometry.options['name'])
## Index first and last points in paths ## Index first and last points in paths
@ -5113,17 +5186,22 @@ class CNCjob(Geometry):
self.pp_geometry = self.app.postprocessors[self.pp_geometry_name] self.pp_geometry = self.app.postprocessors[self.pp_geometry_name]
p = self.pp_geometry p = self.pp_geometry
self.oldx = 0.0
self.oldy = 0.0
self.gcode = self.doformat(p.start_code) self.gcode = self.doformat(p.start_code)
self.gcode += self.doformat(p.feedrate_code) # sets the feed rate self.gcode += self.doformat(p.feedrate_code) # sets the feed rate
self.gcode += self.doformat(p.lift_code, x=0, y=0) # Move (up) to travel height if toolchange is False:
self.gcode += self.doformat(p.startz_code, x=0, y=0) self.gcode += self.doformat(p.lift_code, x=self.oldx , y=self.oldy ) # Move (up) to travel height
self.gcode += self.doformat(p.startz_code, x=self.oldx , y=self.oldy )
if toolchange: if toolchange:
if "line_xyz" in self.pp_geometry_name: # if "line_xyz" in self.pp_geometry_name:
self.gcode += self.doformat(p.toolchange_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1]) # self.gcode += self.doformat(p.toolchange_code, x=self.toolchange_xy[0], y=self.toolchange_xy[1])
else: # else:
# self.gcode += self.doformat(p.toolchange_code)
self.gcode += self.doformat(p.toolchange_code) self.gcode += self.doformat(p.toolchange_code)
self.gcode += self.doformat(p.spindle_code) # Spindle start self.gcode += self.doformat(p.spindle_code) # Spindle start
@ -5328,10 +5406,17 @@ class CNCjob(Geometry):
# Current path: temporary storage until tool is # Current path: temporary storage until tool is
# lifted or lowered. # lifted or lowered.
if self.toolchange_xy == "excellon": if self.toolchange_xy_type == "excellon":
if self.app.defaults["excellon_toolchangexy"] == '':
pos_xy = [0, 0]
else:
pos_xy = [float(eval(a)) for a in self.app.defaults["excellon_toolchangexy"].split(",")] pos_xy = [float(eval(a)) for a in self.app.defaults["excellon_toolchangexy"].split(",")]
else:
if self.app.defaults["geometry_toolchangexy"] == '':
pos_xy = [0, 0]
else: else:
pos_xy = [float(eval(a)) for a in self.app.defaults["geometry_toolchangexy"].split(",")] pos_xy = [float(eval(a)) for a in self.app.defaults["geometry_toolchangexy"].split(",")]
path = [pos_xy] path = [pos_xy]
# path = [(0, 0)] # path = [(0, 0)]
@ -5456,7 +5541,7 @@ class CNCjob(Geometry):
def plot2(self, tooldia=None, dpi=75, margin=0.1, gcode_parsed=None, def plot2(self, tooldia=None, dpi=75, margin=0.1, gcode_parsed=None,
color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]}, color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]},
alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, obj=None, visible=False): alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, obj=None, visible=False, kind='all'):
""" """
Plots the G-code job onto the given axes. Plots the G-code job onto the given axes.
@ -5477,7 +5562,15 @@ class CNCjob(Geometry):
if tooldia == 0: if tooldia == 0:
for geo in gcode_parsed: for geo in gcode_parsed:
if kind == 'all':
obj.add_shape(shape=geo['geom'], color=color[geo['kind'][0]][1], visible=visible) obj.add_shape(shape=geo['geom'], color=color[geo['kind'][0]][1], visible=visible)
elif kind == 'travel':
if geo['kind'][0] == 'T':
obj.add_shape(shape=geo['geom'], color=color['T'][1], visible=visible)
elif kind == 'cut':
if geo['kind'][0] == 'C':
obj.add_shape(shape=geo['geom'], color=color['C'][1], visible=visible)
else: else:
text = [] text = []
pos = [] pos = []
@ -5488,8 +5581,17 @@ class CNCjob(Geometry):
pos.append(geo['geom'].coords[0]) pos.append(geo['geom'].coords[0])
poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance)
if kind == 'all':
obj.add_shape(shape=poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], obj.add_shape(shape=poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0],
visible=visible, layer=1 if geo['kind'][0] == 'C' else 2) visible=visible, layer=1 if geo['kind'][0] == 'C' else 2)
elif kind == 'travel':
if geo['kind'][0] == 'T':
obj.add_shape(shape=poly, color=color['T'][1], face_color=color['T'][0],
visible=visible, layer=2)
elif kind == 'cut':
if geo['kind'][0] == 'C':
obj.add_shape(shape=poly, color=color['C'][1], face_color=color['C'][0],
visible=visible, layer=1)
obj.annotation.set(text=text, pos=pos, visible=obj.options['plot']) obj.annotation.set(text=text, pos=pos, visible=obj.options['plot'])
@ -5798,6 +5900,7 @@ class CNCjob(Geometry):
else: else:
# it's a Shapely object, return it's bounds # it's a Shapely object, return it's bounds
return obj.bounds return obj.bounds
if self.multitool is False: if self.multitool is False:
log.debug("CNCJob->bounds()") log.debug("CNCJob->bounds()")
if self.solid_geometry is None: if self.solid_geometry is None:
@ -5806,21 +5909,30 @@ class CNCjob(Geometry):
bounds_coords = bounds_rec(self.solid_geometry) bounds_coords = bounds_rec(self.solid_geometry)
else: else:
for k, v in self.cnc_tools.items(): for k, v in self.cnc_tools.items():
minx = Inf minx = Inf
miny = Inf miny = Inf
maxx = -Inf maxx = -Inf
maxy = -Inf maxy = -Inf
try:
for k in v['solid_geometry']: for k in v['solid_geometry']:
minx_, miny_, maxx_, maxy_ = bounds_rec(k) minx_, miny_, maxx_, maxy_ = bounds_rec(k)
minx = min(minx, minx_) minx = min(minx, minx_)
miny = min(miny, miny_) miny = min(miny, miny_)
maxx = max(maxx, maxx_) maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_) maxy = max(maxy, maxy_)
except TypeError:
minx_, miny_, maxx_, maxy_ = bounds_rec(v['solid_geometry'])
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
bounds_coords = minx, miny, maxx, maxy bounds_coords = minx, miny, maxx, maxy
return bounds_coords return bounds_coords
# TODO This function should be replaced at some point with a "real" function. Until then it's an ugly hack ...
def scale(self, xfactor, yfactor=None, point=None): def scale(self, xfactor, yfactor=None, point=None):
""" """
Scales all the geometry on the XY plane in the object by the Scales all the geometry on the XY plane in the object by the
@ -5844,8 +5956,124 @@ class CNCjob(Geometry):
else: else:
px, py = point px, py = point
def scale_g(g):
"""
:param g: 'g' parameter it's a gcode string
:return: scaled gcode string
"""
temp_gcode = ''
header_start = False
header_stop = False
units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
lines = StringIO(g)
for line in lines:
# this changes the GCODE header ---- UGLY HACK
if "TOOL DIAMETER" in line or "Feedrate:" in line:
header_start = True
if "G20" in line or "G21" in line:
header_start = False
header_stop = True
if header_start is True:
header_stop = False
if "in" in line:
if units == 'MM':
line = line.replace("in", "mm")
if "mm" in line:
if units == 'IN':
line = line.replace("mm", "in")
# find any float number in header (even multiple on the same line) and convert it
numbers_in_header = re.findall(self.g_nr_re, line)
if numbers_in_header:
for nr in numbers_in_header:
new_nr = float(nr) * xfactor
# replace the updated string
line = line.replace(nr, ('%.*f' % (self.app.defaults["cncjob_coords_decimals"], new_nr))
)
# this scales all the X and Y and Z and F values and also the Tool Dia in the toolchange message
if header_stop is True:
if "G20" in line:
if units == 'MM':
line = line.replace("G20", "G21")
if "G21" in line:
if units == 'IN':
line = line.replace("G21", "G20")
# find the X group
match_x = self.g_x_re.search(line)
if match_x:
if match_x.group(1) is not None:
new_x = float(match_x.group(1)[1:]) * xfactor
# replace the updated string
line = line.replace(
match_x.group(1),
'X%.*f' % (self.app.defaults["cncjob_coords_decimals"], new_x)
)
# find the Y group
match_y = self.g_y_re.search(line)
if match_y:
if match_y.group(1) is not None:
new_y = float(match_y.group(1)[1:]) * yfactor
line = line.replace(
match_y.group(1),
'Y%.*f' % (self.app.defaults["cncjob_coords_decimals"], new_y)
)
# find the Z group
match_z = self.g_z_re.search(line)
if match_z:
if match_z.group(1) is not None:
new_z = float(match_z.group(1)[1:]) * xfactor
line = line.replace(
match_z.group(1),
'Z%.*f' % (self.app.defaults["cncjob_coords_decimals"], new_z)
)
# find the F group
match_f = self.g_f_re.search(line)
if match_f:
if match_f.group(1) is not None:
new_f = float(match_f.group(1)[1:]) * xfactor
line = line.replace(
match_f.group(1),
'F%.*f' % (self.app.defaults["cncjob_fr_decimals"], new_f)
)
# find the T group (tool dia on toolchange)
match_t = self.g_t_re.search(line)
if match_t:
if match_t.group(1) is not None:
new_t = float(match_t.group(1)[1:]) * xfactor
line = line.replace(
match_t.group(1),
'= %.*f' % (self.app.defaults["cncjob_coords_decimals"], new_t)
)
temp_gcode += line
lines.close()
header_stop = False
return temp_gcode
if self.multitool is False:
# offset Gcode
self.gcode = scale_g(self.gcode)
# offset geometry
for g in self.gcode_parsed: for g in self.gcode_parsed:
g['geom'] = affinity.scale(g['geom'], xfactor, yfactor, origin=(px, py)) g['geom'] = affinity.scale(g['geom'], xfactor, yfactor, origin=(px, py))
self.create_geometry()
else:
for k, v in self.cnc_tools.items():
# scale Gcode
v['gcode'] = scale_g(v['gcode'])
# scale gcode_parsed
for g in v['gcode_parsed']:
g['geom'] = affinity.scale(g['geom'], xfactor, yfactor, origin=(px, py))
v['solid_geometry'] = cascaded_union([geo['geom'] for geo in v['gcode_parsed']])
self.create_geometry() self.create_geometry()
@ -5875,7 +6103,7 @@ class CNCjob(Geometry):
lines = StringIO(g) lines = StringIO(g)
for line in lines: for line in lines:
# find the X group # find the X group
match_x = self.g_offsetx_re.search(line) match_x = self.g_x_re.search(line)
if match_x: if match_x:
if match_x.group(1) is not None: if match_x.group(1) is not None:
# get the coordinate and add X offset # get the coordinate and add X offset
@ -5885,7 +6113,7 @@ class CNCjob(Geometry):
match_x.group(1), match_x.group(1),
'X%.*f' % (self.app.defaults["cncjob_coords_decimals"], new_x) 'X%.*f' % (self.app.defaults["cncjob_coords_decimals"], new_x)
) )
match_y = self.g_offsety_re.search(line) match_y = self.g_y_re.search(line)
if match_y: if match_y:
if match_y.group(1) is not None: if match_y.group(1) is not None:
new_y = float(match_y.group(1)[1:]) + dy new_y = float(match_y.group(1)[1:]) + dy

View File

@ -10,6 +10,7 @@ class ToolCalculator(FlatCAMTool):
toolName = "Calculators" toolName = "Calculators"
v_shapeName = "V-Shape Tool Calculator" v_shapeName = "V-Shape Tool Calculator"
unitsName = "Units Calculator" unitsName = "Units Calculator"
eplateName = "ElectroPlating Calculator"
def __init__(self, app): def __init__(self, app):
FlatCAMTool.__init__(self, app) FlatCAMTool.__init__(self, app)
@ -20,7 +21,9 @@ class ToolCalculator(FlatCAMTool):
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName) title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
self.layout.addWidget(title_label) self.layout.addWidget(title_label)
## V-shape Tool Calculator ############################
## V-shape Tool Calculator ##
############################
self.v_shape_spacer_label = QtWidgets.QLabel(" ") self.v_shape_spacer_label = QtWidgets.QLabel(" ")
self.layout.addWidget(self.v_shape_spacer_label) self.layout.addWidget(self.v_shape_spacer_label)
@ -35,30 +38,30 @@ class ToolCalculator(FlatCAMTool):
self.tipDia_label = QtWidgets.QLabel("Tip Diameter:") self.tipDia_label = QtWidgets.QLabel("Tip Diameter:")
self.tipDia_entry = FCEntry() self.tipDia_entry = FCEntry()
self.tipDia_entry.setFixedWidth(70) # self.tipDia_entry.setFixedWidth(70)
self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.tipDia_entry.setToolTip('This is the diameter of the tool tip.\n' self.tipDia_label.setToolTip('This is the diameter of the tool tip.\n'
'The manufacturer specifies it.') 'The manufacturer specifies it.')
self.tipAngle_label = QtWidgets.QLabel("Tip Angle:") self.tipAngle_label = QtWidgets.QLabel("Tip Angle:")
self.tipAngle_entry = FCEntry() self.tipAngle_entry = FCEntry()
self.tipAngle_entry.setFixedWidth(70) # self.tipAngle_entry.setFixedWidth(70)
self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.tipAngle_entry.setToolTip("This is the angle of the tip of the tool.\n" self.tipAngle_label.setToolTip("This is the angle of the tip of the tool.\n"
"It is specified by manufacturer.") "It is specified by manufacturer.")
self.cutDepth_label = QtWidgets.QLabel("Cut Z:") self.cutDepth_label = QtWidgets.QLabel("Cut Z:")
self.cutDepth_entry = FCEntry() self.cutDepth_entry = FCEntry()
self.cutDepth_entry.setFixedWidth(70) # self.cutDepth_entry.setFixedWidth(70)
self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.cutDepth_entry.setToolTip("This is the depth to cut into the material.\n" self.cutDepth_label.setToolTip("This is the depth to cut into the material.\n"
"In the CNCJob is the CutZ parameter.") "In the CNCJob is the CutZ parameter.")
self.effectiveToolDia_label = QtWidgets.QLabel("Tool Diameter:") self.effectiveToolDia_label = QtWidgets.QLabel("Tool Diameter:")
self.effectiveToolDia_entry = FCEntry() self.effectiveToolDia_entry = FCEntry()
self.effectiveToolDia_entry.setFixedWidth(70) # self.effectiveToolDia_entry.setFixedWidth(70)
self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.effectiveToolDia_entry.setToolTip("This is the tool diameter to be entered into\n" self.effectiveToolDia_label.setToolTip("This is the tool diameter to be entered into\n"
"FlatCAM Gerber section.\n" "FlatCAM Gerber section.\n"
"In the CNCJob section it is called >Tool dia<.") "In the CNCJob section it is called >Tool dia<.")
# self.effectiveToolDia_entry.setEnabled(False) # self.effectiveToolDia_entry.setEnabled(False)
@ -69,19 +72,21 @@ class ToolCalculator(FlatCAMTool):
form_layout.addRow(self.cutDepth_label, self.cutDepth_entry) form_layout.addRow(self.cutDepth_label, self.cutDepth_entry)
form_layout.addRow(self.effectiveToolDia_label, self.effectiveToolDia_entry) form_layout.addRow(self.effectiveToolDia_label, self.effectiveToolDia_entry)
## Buttons ## Buttons
self.calculate_button = QtWidgets.QPushButton("Calculate") self.calculate_vshape_button = QtWidgets.QPushButton("Calculate")
self.calculate_button.setFixedWidth(70) # self.calculate_button.setFixedWidth(70)
self.calculate_button.setToolTip( self.calculate_vshape_button.setToolTip(
"Calculate either the Cut Z or the effective tool diameter,\n " "Calculate either the Cut Z or the effective tool diameter,\n "
"depending on which is desired and which is known. " "depending on which is desired and which is known. "
) )
self.empty_label = QtWidgets.QLabel(" ") self.empty_label = QtWidgets.QLabel(" ")
form_layout.addRow(self.empty_label, self.calculate_button) form_layout.addRow(self.empty_label, self.calculate_vshape_button)
######################
## Units Calculator ##
######################
## Units Calculator
self.unists_spacer_label = QtWidgets.QLabel(" ") self.unists_spacer_label = QtWidgets.QLabel(" ")
self.layout.addWidget(self.unists_spacer_label) self.layout.addWidget(self.unists_spacer_label)
@ -89,25 +94,109 @@ class ToolCalculator(FlatCAMTool):
units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName) units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName)
self.layout.addWidget(units_label) self.layout.addWidget(units_label)
#Form Layout #Grid Layout
form_units_layout = QtWidgets.QFormLayout() grid_units_layout = QtWidgets.QGridLayout()
self.layout.addLayout(form_units_layout) self.layout.addLayout(grid_units_layout)
inch_label = QtWidgets.QLabel("INCH") inch_label = QtWidgets.QLabel("INCH")
mm_label = QtWidgets.QLabel("MM") mm_label = QtWidgets.QLabel("MM")
grid_units_layout.addWidget(mm_label, 0, 0)
grid_units_layout.addWidget( inch_label, 0, 1)
self.inch_entry = FCEntry() self.inch_entry = FCEntry()
self.inch_entry.setFixedWidth(70) # self.inch_entry.setFixedWidth(70)
self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.inch_entry.setToolTip("Here you enter the value to be converted from INCH to MM") self.inch_entry.setToolTip("Here you enter the value to be converted from INCH to MM")
self.mm_entry = FCEntry() self.mm_entry = FCEntry()
self.mm_entry.setFixedWidth(70) # self.mm_entry.setFixedWidth(130)
self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.mm_entry.setToolTip("Here you enter the value to be converted from MM to INCH") self.mm_entry.setToolTip("Here you enter the value to be converted from MM to INCH")
form_units_layout.addRow(mm_label, inch_label) grid_units_layout.addWidget(self.mm_entry, 1, 0)
form_units_layout.addRow(self.mm_entry, self.inch_entry) grid_units_layout.addWidget(self.inch_entry, 1, 1)
####################################
## ElectroPlating Tool Calculator ##
####################################
self.plate_spacer_label = QtWidgets.QLabel(" ")
self.layout.addWidget(self.plate_spacer_label)
## Title of the ElectroPlating Tools Calculator
plate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.eplateName)
plate_title_label.setToolTip(
"This calculator is useful for those who plate the via/pad/drill holes,\n"
"using a method like grahite ink or calcium hypophosphite ink or palladium chloride."
)
self.layout.addWidget(plate_title_label)
## Plate Form Layout
plate_form_layout = QtWidgets.QFormLayout()
self.layout.addLayout(plate_form_layout)
self.pcblengthlabel = QtWidgets.QLabel("Board Length:")
self.pcblength_entry = FCEntry()
# self.pcblengthlabel.setFixedWidth(70)
self.pcblength_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.pcblengthlabel.setToolTip('This is the board length. In centimeters.')
self.pcbwidthlabel = QtWidgets.QLabel("Board Width:")
self.pcbwidth_entry = FCEntry()
# self.pcbwidthlabel.setFixedWidth(70)
self.pcbwidth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.pcbwidthlabel.setToolTip('This is the board width.In centimeters.')
self.cdensity_label = QtWidgets.QLabel("Current Density:")
self.cdensity_entry = FCEntry()
# self.cdensity_entry.setFixedWidth(70)
self.cdensity_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.cdensity_label.setToolTip("Current density to pass through the board. \n"
"In Amps per Square Feet ASF.")
self.growth_label = QtWidgets.QLabel("Copper Growth:")
self.growth_entry = FCEntry()
# self.growth_entry.setFixedWidth(70)
self.growth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.growth_label.setToolTip("How thick the copper growth is intended to be.\n"
"In microns.")
# self.growth_entry.setEnabled(False)
self.cvaluelabel = QtWidgets.QLabel("Current Value:")
self.cvalue_entry = FCEntry()
# self.cvaluelabel.setFixedWidth(70)
self.cvalue_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.cvaluelabel.setToolTip('This is the current intensity value\n'
'to be set on the Power Supply. In Amps.')
self.cvalue_entry.setDisabled(True)
self.timelabel = QtWidgets.QLabel("Time:")
self.time_entry = FCEntry()
# self.timelabel.setFixedWidth(70)
self.time_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.timelabel.setToolTip('This is the calculated time required for the procedure.\n'
'In minutes.')
self.time_entry.setDisabled(True)
plate_form_layout.addRow(self.pcblengthlabel, self.pcblength_entry)
plate_form_layout.addRow(self.pcbwidthlabel, self.pcbwidth_entry)
plate_form_layout.addRow(self.cdensity_label, self.cdensity_entry)
plate_form_layout.addRow(self.growth_label, self.growth_entry)
plate_form_layout.addRow(self.cvaluelabel, self.cvalue_entry)
plate_form_layout.addRow(self.timelabel, self.time_entry)
## Buttons
self.calculate_plate_button = QtWidgets.QPushButton("Calculate")
# self.calculate_button.setFixedWidth(70)
self.calculate_plate_button.setToolTip(
"Calculate the current intensity value and the procedure time,\n "
"depending on the parameters above"
)
self.empty_label_2 = QtWidgets.QLabel(" ")
plate_form_layout.addRow(self.empty_label_2, self.calculate_plate_button)
self.layout.addStretch() self.layout.addStretch()
@ -116,13 +205,36 @@ class ToolCalculator(FlatCAMTool):
self.cutDepth_entry.editingFinished.connect(self.on_calculate_tool_dia) self.cutDepth_entry.editingFinished.connect(self.on_calculate_tool_dia)
self.tipDia_entry.editingFinished.connect(self.on_calculate_tool_dia) self.tipDia_entry.editingFinished.connect(self.on_calculate_tool_dia)
self.tipAngle_entry.editingFinished.connect(self.on_calculate_tool_dia) self.tipAngle_entry.editingFinished.connect(self.on_calculate_tool_dia)
self.calculate_button.clicked.connect(self.on_calculate_tool_dia) self.calculate_vshape_button.clicked.connect(self.on_calculate_tool_dia)
self.mm_entry.editingFinished.connect(self.on_calculate_inch_units) self.mm_entry.editingFinished.connect(self.on_calculate_inch_units)
self.inch_entry.editingFinished.connect(self.on_calculate_mm_units) self.inch_entry.editingFinished.connect(self.on_calculate_mm_units)
self.calculate_plate_button.clicked.connect(self.on_calculate_eplate)
def run(self):
self.app.report_usage("ToolCalculators()")
FlatCAMTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "Calc. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
def set_tool_ui(self):
## Initialize form ## Initialize form
self.mm_entry.set_value('0')
self.inch_entry.set_value('0')
self.pcblength_entry.set_value('10')
self.pcbwidth_entry.set_value('10')
self.cdensity_entry.set_value('13')
self.growth_entry.set_value('10')
self.cvalue_entry.set_value(2.80)
self.time_entry.set_value(33.0)
if self.app.defaults["units"] == 'MM': if self.app.defaults["units"] == 'MM':
self.tipDia_entry.set_value('0.2') self.tipDia_entry.set_value('0.2')
self.tipAngle_entry.set_value('45') self.tipAngle_entry.set_value('45')
@ -134,16 +246,6 @@ class ToolCalculator(FlatCAMTool):
self.cutDepth_entry.set_value('9.84252') self.cutDepth_entry.set_value('9.84252')
self.effectiveToolDia_entry.set_value('15.35433') self.effectiveToolDia_entry.set_value('15.35433')
self.mm_entry.set_value('0')
self.inch_entry.set_value('0')
def run(self):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Calc. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
def on_calculate_tool_dia(self): def on_calculate_tool_dia(self):
# Calculation: # Calculation:
# Manufacturer gives total angle of the the tip but we need only half of it # Manufacturer gives total angle of the the tip but we need only half of it
@ -155,18 +257,117 @@ class ToolCalculator(FlatCAMTool):
try: try:
tip_diameter = float(self.tipDia_entry.get_value()) tip_diameter = float(self.tipDia_entry.get_value())
half_tip_angle = float(self.tipAngle_entry.get_value()) / 2 except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
tip_diameter = float(self.tipDia_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
half_tip_angle = float(self.tipAngle_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
half_tip_angle = float(self.tipAngle_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
half_tip_angle /= 2
try:
cut_depth = float(self.cutDepth_entry.get_value()) cut_depth = float(self.cutDepth_entry.get_value())
except: except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
cut_depth = float(self.cutDepth_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return return
tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle))) tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle)))
self.effectiveToolDia_entry.set_value("%.4f" % tool_diameter) self.effectiveToolDia_entry.set_value("%.4f" % tool_diameter)
def on_calculate_inch_units(self): def on_calculate_inch_units(self):
self.inch_entry.set_value('%.6f' % (float(self.mm_entry.get_value()) / 25.4)) try:
mm_val = float(self.mm_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
mm_val = float(self.mm_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
self.inch_entry.set_value('%.6f' % (mm_val / 25.4))
def on_calculate_mm_units(self): def on_calculate_mm_units(self):
self.mm_entry.set_value('%.6f' % (float(self.inch_entry.get_value()) * 25.4)) try:
inch_val = float(self.inch_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
inch_val = float(self.inch_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
self.mm_entry.set_value('%.6f' % (inch_val * 25.4))
def on_calculate_eplate(self):
try:
length = float(self.pcblength_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
length = float(self.pcblength_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
width = float(self.pcbwidth_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
width = float(self.pcbwidth_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
density = float(self.cdensity_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
density = float(self.cdensity_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
copper = float(self.growth_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
copper = float(self.growth_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
calculated_current = (length * width * density) * 0.0021527820833419
calculated_time = copper * 2.142857142857143 * float(20 / density)
self.cvalue_entry.set_value('%.2f' % calculated_current)
self.time_entry.set_value('%.1f' % calculated_time)
# end of file # end of file

View File

@ -7,7 +7,8 @@ from GUIElements import IntEntry, RadioSet, LengthEntry
from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber from FlatCAMObj import FlatCAMGeometry, FlatCAMExcellon, FlatCAMGerber
class ToolCutout(FlatCAMTool):
class ToolCutOut(FlatCAMTool):
toolName = "Cutout PCB" toolName = "Cutout PCB"
@ -48,6 +49,7 @@ class ToolCutout(FlatCAMTool):
self.obj_combo.setModel(self.app.collection) self.obj_combo.setModel(self.app.collection)
self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.obj_combo.setCurrentIndex(1) self.obj_combo.setCurrentIndex(1)
self.object_label = QtWidgets.QLabel("Object:") self.object_label = QtWidgets.QLabel("Object:")
self.object_label.setToolTip( self.object_label.setToolTip(
"Object to be cutout. " "Object to be cutout. "
@ -172,11 +174,11 @@ class ToolCutout(FlatCAMTool):
self.layout.addStretch() self.layout.addStretch()
## Init GUI ## Init GUI
self.dia.set_value(1) # self.dia.set_value(1)
self.margin.set_value(0) # self.margin.set_value(0)
self.gapsize.set_value(1) # self.gapsize.set_value(1)
self.gaps.set_value(4) # self.gaps.set_value(4)
self.gaps_rect_radio.set_value("4") # self.gaps_rect_radio.set_value("4")
## Signals ## Signals
self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout) self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
@ -190,14 +192,18 @@ class ToolCutout(FlatCAMTool):
self.obj_combo.setCurrentIndex(0) self.obj_combo.setCurrentIndex(0)
def run(self): def run(self):
self.app.report_usage("ToolCutOut()")
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.set_ui() self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "Cutout Tool") self.app.ui.notebook.setTabText(2, "Cutout Tool")
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+U', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+U', **kwargs)
def set_ui(self): def set_tool_ui(self):
self.reset_fields()
self.dia.set_value(float(self.app.defaults["tools_cutouttooldia"])) self.dia.set_value(float(self.app.defaults["tools_cutouttooldia"]))
self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"])) self.margin.set_value(float(self.app.defaults["tools_cutoutmargin"]))
self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"])) self.gapsize.set_value(float(self.app.defaults["tools_cutoutgapsize"]))
@ -216,45 +222,63 @@ class ToolCutout(FlatCAMTool):
try: try:
cutout_obj = self.app.collection.get_by_name(str(name)) cutout_obj = self.app.collection.get_by_name(str(name))
except: except:
self.app.inform.emit("[error_notcl]Could not retrieve object: %s" % name) self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % name)
return "Could not retrieve object: %s" % name return "Could not retrieve object: %s" % name
if cutout_obj is None: if cutout_obj is None:
self.app.inform.emit("[error_notcl]There is no object selected for Cutout.\nSelect one and try again.") self.app.inform.emit("[ERROR_NOTCL]There is no object selected for Cutout.\nSelect one and try again.")
return return
try: try:
dia = float(self.dia.get_value()) dia = float(self.dia.get_value())
except TypeError: except ValueError:
self.app.inform.emit("[warning_notcl] Tool diameter value is missing. Add it and retry.") # try to convert comma to decimal point. if it's still not working error message and return
try:
dia = float(self.dia.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
"Add it and retry.")
return return
try: try:
margin = float(self.margin.get_value()) margin = float(self.margin.get_value())
except TypeError: except ValueError:
self.app.inform.emit("[warning_notcl] Margin value is missing. Add it and retry.") # try to convert comma to decimal point. if it's still not working error message and return
try:
margin = float(self.margin.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[WARNING_NOTCL] Margin value is missing or wrong format. "
"Add it and retry.")
return return
try: try:
gapsize = float(self.gapsize.get_value()) gapsize = float(self.gapsize.get_value())
except TypeError: except ValueError:
self.app.inform.emit("[warning_notcl] Gap size value is missing. Add it and retry.") # try to convert comma to decimal point. if it's still not working error message and return
try:
gapsize = float(self.gapsize.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[WARNING_NOTCL] Gap size value is missing or wrong format. "
"Add it and retry.")
return return
try: try:
gaps = self.gaps.get_value() gaps = self.gaps.get_value()
except TypeError: except TypeError:
self.app.inform.emit("[warning_notcl] Number of gaps value is missing. Add it and retry.") self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
return return
if 0 in {dia}: if 0 in {dia}:
self.app.inform.emit("[warning_notcl]Tool Diameter is zero value. Change it to a positive integer.") self.app.inform.emit("[WARNING_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
return "Tool Diameter is zero value. Change it to a positive integer." return "Tool Diameter is zero value. Change it to a positive integer."
if gaps not in ['lr', 'tb', '2lr', '2tb', '4', '8']: if gaps not in ['lr', 'tb', '2lr', '2tb', '4', '8']:
self.app.inform.emit("[warning_notcl] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. " self.app.inform.emit("[WARNING_NOTCL] Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
"Fill in a correct value and retry. ") "Fill in a correct value and retry. ")
return return
if cutout_obj.multigeo is True: if cutout_obj.multigeo is True:
self.app.inform.emit("[error]Cutout operation cannot be done on a multi-geo Geometry.\n" self.app.inform.emit("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n" "Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
"and after that perform Cutout.") "and after that perform Cutout.")
return return
@ -338,39 +362,57 @@ class ToolCutout(FlatCAMTool):
try: try:
cutout_obj = self.app.collection.get_by_name(str(name)) cutout_obj = self.app.collection.get_by_name(str(name))
except: except:
self.app.inform.emit("[error_notcl]Could not retrieve object: %s" % name) self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % name)
return "Could not retrieve object: %s" % name return "Could not retrieve object: %s" % name
if cutout_obj is None: if cutout_obj is None:
self.app.inform.emit("[error_notcl]Object not found: %s" % cutout_obj) self.app.inform.emit("[ERROR_NOTCL]Object not found: %s" % cutout_obj)
try: try:
dia = float(self.dia.get_value()) dia = float(self.dia.get_value())
except TypeError: except ValueError:
self.app.inform.emit("[warning_notcl] Tool diameter value is missing. Add it and retry.") # try to convert comma to decimal point. if it's still not working error message and return
try:
dia = float(self.dia.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[WARNING_NOTCL] Tool diameter value is missing or wrong format. "
"Add it and retry.")
return return
try: try:
margin = float(self.margin.get_value()) margin = float(self.margin.get_value())
except TypeError: except ValueError:
self.app.inform.emit("[warning_notcl] Margin value is missing. Add it and retry.") # try to convert comma to decimal point. if it's still not working error message and return
try:
margin = float(self.margin.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[WARNING_NOTCL] Margin value is missing or wrong format. "
"Add it and retry.")
return return
try: try:
gapsize = float(self.gapsize.get_value()) gapsize = float(self.gapsize.get_value())
except TypeError: except ValueError:
self.app.inform.emit("[warning_notcl] Gap size value is missing. Add it and retry.") # try to convert comma to decimal point. if it's still not working error message and return
try:
gapsize = float(self.gapsize.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[WARNING_NOTCL] Gap size value is missing or wrong format. "
"Add it and retry.")
return return
try: try:
gaps = self.gaps_rect_radio.get_value() gaps = self.gaps_rect_radio.get_value()
except TypeError: except TypeError:
self.app.inform.emit("[warning_notcl] Number of gaps value is missing. Add it and retry.") self.app.inform.emit("[WARNING_NOTCL] Number of gaps value is missing. Add it and retry.")
return return
if 0 in {dia}: if 0 in {dia}:
self.app.inform.emit("[error_notcl]Tool Diameter is zero value. Change it to a positive integer.") self.app.inform.emit("[ERROR_NOTCL]Tool Diameter is zero value. Change it to a positive integer.")
return "Tool Diameter is zero value. Change it to a positive integer." return "Tool Diameter is zero value. Change it to a positive integer."
if cutout_obj.multigeo is True: if cutout_obj.multigeo is True:
self.app.inform.emit("[error]Cutout operation cannot be done on a multi-geo Geometry.\n" self.app.inform.emit("[ERROR]Cutout operation cannot be done on a multi-geo Geometry.\n"
"Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n" "Optionally, this Multi-geo Geometry can be converted to Single-geo Geometry,\n"
"and after that perform Cutout.") "and after that perform Cutout.")
return return

View File

@ -6,6 +6,7 @@ from shapely.geometry import Point
from shapely import affinity from shapely import affinity
from PyQt5 import QtCore from PyQt5 import QtCore
class DblSidedTool(FlatCAMTool): class DblSidedTool(FlatCAMTool):
toolName = "2-Sided PCB" toolName = "2-Sided PCB"
@ -115,8 +116,8 @@ class DblSidedTool(FlatCAMTool):
self.axloc_label = QtWidgets.QLabel("Axis Ref:") self.axloc_label = QtWidgets.QLabel("Axis Ref:")
self.axloc_label.setToolTip( self.axloc_label.setToolTip(
"The axis should pass through a <b>point</b> or cut\n " "The axis should pass through a <b>point</b> or cut\n "
"a specified <b>box</b> (in a Geometry object) in \n" "a specified <b>box</b> (in a FlatCAM object) through \n"
"the middle." "the center."
) )
# grid_lay.addRow("Axis Location:", self.axis_location) # grid_lay.addRow("Axis Location:", self.axis_location)
grid_lay.addWidget(self.axloc_label, 8, 0) grid_lay.addWidget(self.axloc_label, 8, 0)
@ -129,19 +130,18 @@ class DblSidedTool(FlatCAMTool):
self.point_box_container = QtWidgets.QVBoxLayout() self.point_box_container = QtWidgets.QVBoxLayout()
self.pb_label = QtWidgets.QLabel("<b>Point/Box:</b>") self.pb_label = QtWidgets.QLabel("<b>Point/Box:</b>")
self.pb_label.setToolTip( self.pb_label.setToolTip(
"Specify the point (x, y) through which the mirror axis \n " "If 'Point' is selected above it store the coordinates (x, y) through which\n"
"passes or the Geometry object containing a rectangle \n" "the mirroring axis passes.\n"
"that the mirror axis cuts in half." "If 'Box' is selected above, select here a FlatCAM object (Gerber, Exc or Geo).\n"
"Through the center of this object pass the mirroring axis selected above."
) )
# grid_lay.addRow("Point/Box:", self.point_box_container)
self.add_point_button = QtWidgets.QPushButton("Add") self.add_point_button = QtWidgets.QPushButton("Add")
self.add_point_button.setToolTip( self.add_point_button.setToolTip(
"Add the <b>point (x, y)</b> through which the mirror axis \n " "Add the coordinates in format <b>(x, y)</b> through which the mirroring axis \n "
"passes or the Object containing a rectangle \n" "selected in 'MIRROR AXIS' pass.\n"
"that the mirror axis cuts in half.\n" "The (x, y) coordinates are captured by pressing SHIFT key\n"
"The point is captured by pressing SHIFT key\n" "and left mouse button click on canvas or you can enter the coords manually."
"and left mouse clicking on canvas or you can enter them manually."
) )
self.add_point_button.setFixedWidth(40) self.add_point_button.setFixedWidth(40)
@ -173,9 +173,9 @@ class DblSidedTool(FlatCAMTool):
self.ah_label.setToolTip( self.ah_label.setToolTip(
"Alignment holes (x1, y1), (x2, y2), ... " "Alignment holes (x1, y1), (x2, y2), ... "
"on one side of the mirror axis. For each set of (x, y) coordinates\n" "on one side of the mirror axis. For each set of (x, y) coordinates\n"
"entered here, a pair of drills will be created: one on the\n" "entered here, a pair of drills will be created:\n\n"
"coordinates entered and one in mirror position over the axis\n" "- one drill at the coordinates from the field\n"
"selected above in the 'Mirror Axis'." "- one drill in mirror position over the axis selected above in the 'Mirror Axis'."
) )
self.layout.addWidget(self.ah_label) self.layout.addWidget(self.ah_label)
@ -186,10 +186,13 @@ class DblSidedTool(FlatCAMTool):
self.add_drill_point_button = QtWidgets.QPushButton("Add") self.add_drill_point_button = QtWidgets.QPushButton("Add")
self.add_drill_point_button.setToolTip( self.add_drill_point_button.setToolTip(
"Add alignment drill holes coords (x1, y1), (x2, y2), ... \n" "Add alignment drill holes coords in the format: (x1, y1), (x2, y2), ... \n"
"on one side of the mirror axis.\n" "on one side of the mirror axis.\n\n"
"The point(s) can be captured by pressing SHIFT key\n" "The coordinates set can be obtained:\n"
"and left mouse clicking on canvas. Or you can enter them manually." "- press SHIFT key and left mouse clicking on canvas. Then click Add.\n"
"- press SHIFT key and left mouse clicking on canvas. Then CTRL+V in the field.\n"
"- press SHIFT key and left mouse clicking on canvas. Then RMB click in the field and click Paste.\n"
"- by entering the coords manually in the format: (x1, y1), (x2, y2), ..."
) )
self.add_drill_point_button.setFixedWidth(40) self.add_drill_point_button.setFixedWidth(40)
@ -197,11 +200,10 @@ class DblSidedTool(FlatCAMTool):
grid_lay1.addWidget(self.add_drill_point_button, 0, 3) grid_lay1.addWidget(self.add_drill_point_button, 0, 3)
## Drill diameter for alignment holes ## Drill diameter for alignment holes
self.dt_label = QtWidgets.QLabel("<b>Alignment Drill Creation</b>:") self.dt_label = QtWidgets.QLabel("<b>Alignment Drill Diameter</b>:")
self.dt_label.setToolTip( self.dt_label.setToolTip(
"Create a set of alignment drill holes\n" "Diameter of the drill for the "
"with the specified diameter,\n" "alignment holes."
"at the specified coordinates."
) )
self.layout.addWidget(self.dt_label) self.layout.addWidget(self.dt_label)
@ -249,20 +251,19 @@ class DblSidedTool(FlatCAMTool):
self.drill_values = "" self.drill_values = ""
self.set_ui()
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+D', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+D', **kwargs)
def run(self): def run(self):
self.app.report_usage("Tool2Sided()")
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "2-Sided Tool") self.app.ui.notebook.setTabText(2, "2-Sided Tool")
self.reset_fields()
self.set_ui()
def set_ui(self): def set_tool_ui(self):
## Initialize form self.reset_fields()
self.point_entry.set_value("") self.point_entry.set_value("")
self.alignment_holes.set_value("") self.alignment_holes.set_value("")
@ -283,7 +284,7 @@ class DblSidedTool(FlatCAMTool):
try: try:
px, py = self.point_entry.get_value() px, py = self.point_entry.get_value()
except TypeError: except TypeError:
self.app.inform.emit("[warning_notcl] 'Point' reference is selected and 'Point' coordinates " self.app.inform.emit("[WARNING_NOTCL] 'Point' reference is selected and 'Point' coordinates "
"are missing. Add them and retry.") "are missing. Add them and retry.")
return return
else: else:
@ -297,12 +298,15 @@ class DblSidedTool(FlatCAMTool):
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis] xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
dia = self.drill_dia.get_value() dia = self.drill_dia.get_value()
if dia is None:
self.app.inform.emit("[WARNING_NOTCL]No value or wrong format in Drill Dia entry. Add it and retry.")
return
tools = {"1": {"C": dia}} tools = {"1": {"C": dia}}
# holes = self.alignment_holes.get_value() # holes = self.alignment_holes.get_value()
holes = eval('[{}]'.format(self.alignment_holes.text())) holes = eval('[{}]'.format(self.alignment_holes.text()))
if not holes: if not holes:
self.app.inform.emit("[warning_notcl] There are no Alignment Drill Coordinates to use. Add them and retry.") self.app.inform.emit("[WARNING_NOTCL] There are no Alignment Drill Coordinates to use. Add them and retry.")
return return
drills = [] drills = []
@ -328,11 +332,11 @@ class DblSidedTool(FlatCAMTool):
try: try:
fcobj = model_index.internalPointer().obj fcobj = model_index.internalPointer().obj
except Exception as e: except Exception as e:
self.app.inform.emit("[warning_notcl] There is no Gerber object loaded ...") self.app.inform.emit("[WARNING_NOTCL] There is no Gerber object loaded ...")
return return
if not isinstance(fcobj, FlatCAMGerber): if not isinstance(fcobj, FlatCAMGerber):
self.app.inform.emit("[error_notcl] Only Gerber, Excellon and Geometry objects can be mirrored.") self.app.inform.emit("[ERROR_NOTCL] Only Gerber, Excellon and Geometry objects can be mirrored.")
return return
axis = self.mirror_axis.get_value() axis = self.mirror_axis.get_value()
@ -342,7 +346,7 @@ class DblSidedTool(FlatCAMTool):
try: try:
px, py = self.point_entry.get_value() px, py = self.point_entry.get_value()
except TypeError: except TypeError:
self.app.inform.emit("[warning_notcl] 'Point' coordinates missing. " self.app.inform.emit("[WARNING_NOTCL] 'Point' coordinates missing. "
"Using Origin (0, 0) as mirroring reference.") "Using Origin (0, 0) as mirroring reference.")
px, py = (0, 0) px, py = (0, 0)
@ -352,7 +356,7 @@ class DblSidedTool(FlatCAMTool):
try: try:
bb_obj = model_index_box.internalPointer().obj bb_obj = model_index_box.internalPointer().obj
except Exception as e: except Exception as e:
self.app.inform.emit("[warning_notcl] There is no Box object loaded ...") self.app.inform.emit("[WARNING_NOTCL] There is no Box object loaded ...")
return return
xmin, ymin, xmax, ymax = bb_obj.bounds() xmin, ymin, xmax, ymax = bb_obj.bounds()
@ -370,11 +374,11 @@ class DblSidedTool(FlatCAMTool):
try: try:
fcobj = model_index.internalPointer().obj fcobj = model_index.internalPointer().obj
except Exception as e: except Exception as e:
self.app.inform.emit("[warning_notcl] There is no Excellon object loaded ...") self.app.inform.emit("[WARNING_NOTCL] There is no Excellon object loaded ...")
return return
if not isinstance(fcobj, FlatCAMExcellon): if not isinstance(fcobj, FlatCAMExcellon):
self.app.inform.emit("[error_notcl] Only Gerber, Excellon and Geometry objects can be mirrored.") self.app.inform.emit("[ERROR_NOTCL] Only Gerber, Excellon and Geometry objects can be mirrored.")
return return
axis = self.mirror_axis.get_value() axis = self.mirror_axis.get_value()
@ -388,7 +392,7 @@ class DblSidedTool(FlatCAMTool):
try: try:
bb_obj = model_index_box.internalPointer().obj bb_obj = model_index_box.internalPointer().obj
except Exception as e: except Exception as e:
self.app.inform.emit("[warning_notcl] There is no Box object loaded ...") self.app.inform.emit("[WARNING_NOTCL] There is no Box object loaded ...")
return return
xmin, ymin, xmax, ymax = bb_obj.bounds() xmin, ymin, xmax, ymax = bb_obj.bounds()
@ -406,11 +410,11 @@ class DblSidedTool(FlatCAMTool):
try: try:
fcobj = model_index.internalPointer().obj fcobj = model_index.internalPointer().obj
except Exception as e: except Exception as e:
self.app.inform.emit("[warning_notcl] There is no Geometry object loaded ...") self.app.inform.emit("[WARNING_NOTCL] There is no Geometry object loaded ...")
return return
if not isinstance(fcobj, FlatCAMGeometry): if not isinstance(fcobj, FlatCAMGeometry):
self.app.inform.emit("[error_notcl] Only Gerber, Excellon and Geometry objects can be mirrored.") self.app.inform.emit("[ERROR_NOTCL] Only Gerber, Excellon and Geometry objects can be mirrored.")
return return
axis = self.mirror_axis.get_value() axis = self.mirror_axis.get_value()
@ -424,7 +428,7 @@ class DblSidedTool(FlatCAMTool):
try: try:
bb_obj = model_index_box.internalPointer().obj bb_obj = model_index_box.internalPointer().obj
except Exception as e: except Exception as e:
self.app.inform.emit("[warning_notcl] There is no Box object loaded ...") self.app.inform.emit("[WARNING_NOTCL] There is no Box object loaded ...")
return return
xmin, ymin, xmax, ymax = bb_obj.bounds() xmin, ymin, xmax, ymax = bb_obj.bounds()

View File

@ -1,6 +1,6 @@
from FlatCAMTool import FlatCAMTool from FlatCAMTool import FlatCAMTool
from GUIElements import RadioSet, FloatEntry from GUIElements import RadioSet, FCEntry
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
@ -44,6 +44,7 @@ class Film(FlatCAMTool):
self.tf_object_combo.setModel(self.app.collection) self.tf_object_combo.setModel(self.app.collection)
self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.tf_object_combo.setCurrentIndex(1) self.tf_object_combo.setCurrentIndex(1)
self.tf_object_label = QtWidgets.QLabel("Film Object:") self.tf_object_label = QtWidgets.QLabel("Film Object:")
self.tf_object_label.setToolTip( self.tf_object_label.setToolTip(
"Object for which to create the film." "Object for which to create the film."
@ -101,7 +102,7 @@ class Film(FlatCAMTool):
# Boundary for negative film generation # Boundary for negative film generation
self.boundary_entry = FloatEntry() self.boundary_entry = FCEntry()
self.boundary_label = QtWidgets.QLabel("Border:") self.boundary_label = QtWidgets.QLabel("Border:")
self.boundary_label.setToolTip( self.boundary_label.setToolTip(
"Specify a border around the object.\n" "Specify a border around the object.\n"
@ -115,6 +116,15 @@ class Film(FlatCAMTool):
) )
tf_form_layout.addRow(self.boundary_label, self.boundary_entry) tf_form_layout.addRow(self.boundary_label, self.boundary_entry)
self.film_scale_entry = FCEntry()
self.film_scale_label = QtWidgets.QLabel("Scale Stroke:")
self.film_scale_label.setToolTip(
"Scale the line stroke thickness of each feature in the SVG file.\n"
"It means that the line that envelope each SVG feature will be thicker or thinner,\n"
"therefore the fine features may be more affected by this parameter."
)
tf_form_layout.addRow(self.film_scale_label, self.film_scale_entry)
# Buttons # Buttons
hlay = QtWidgets.QHBoxLayout() hlay = QtWidgets.QHBoxLayout()
self.layout.addLayout(hlay) self.layout.addLayout(hlay)
@ -136,10 +146,6 @@ class Film(FlatCAMTool):
self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed) self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
self.tf_type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed) self.tf_type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
## Initialize form
self.film_type.set_value('neg')
self.boundary_entry.set_value(0.0)
def on_type_obj_index_changed(self, index): def on_type_obj_index_changed(self, index):
obj_type = self.tf_type_obj_combo.currentIndex() obj_type = self.tf_type_obj_combo.currentIndex()
self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex())) self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
@ -151,25 +157,58 @@ class Film(FlatCAMTool):
self.tf_box_combo.setCurrentIndex(0) self.tf_box_combo.setCurrentIndex(0)
def run(self): def run(self):
self.app.report_usage("ToolFilm()")
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "Film Tool") self.app.ui.notebook.setTabText(2, "Film Tool")
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+L', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+L', **kwargs)
def set_tool_ui(self):
self.reset_fields()
f_type = self.app.defaults["tools_film_type"] if self.app.defaults["tools_film_type"] else 'neg'
self.film_type.set_value(str(f_type))
b_entry = self.app.defaults[ "tools_film_boundary"] if self.app.defaults[ "tools_film_boundary"] else 0.0
self.boundary_entry.set_value(float(b_entry))
scale_stroke_width = self.app.defaults["tools_film_scale"] if self.app.defaults["tools_film_scale"] else 0.0
self.film_scale_entry.set_value(int(scale_stroke_width))
def on_film_creation(self): def on_film_creation(self):
try: try:
name = self.tf_object_combo.currentText() name = self.tf_object_combo.currentText()
except: except:
self.app.inform.emit("[error_notcl] No Film object selected. Load a Film object and retry.") self.app.inform.emit("[ERROR_NOTCL] No FlatCAM object selected. Load an object for Film and retry.")
return return
try: try:
boxname = self.tf_box_combo.currentText() boxname = self.tf_box_combo.currentText()
except: except:
self.app.inform.emit("[error_notcl] No Box object selected. Load a Box object and retry.") self.app.inform.emit("[ERROR_NOTCL] No FlatCAM object selected. Load an object for Box and retry.")
return return
try:
border = float(self.boundary_entry.get_value()) border = float(self.boundary_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
border = float(self.boundary_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
scale_stroke_width = int(self.film_scale_entry.get_value())
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
if border is None: if border is None:
border = 0 border = 0
@ -177,32 +216,36 @@ class Film(FlatCAMTool):
if self.film_type.get_value() == "pos": if self.film_type.get_value() == "pos":
try: try:
filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export SVG positive", filename, _ = QtWidgets.QFileDialog.getSaveFileName(
directory=self.app.get_last_save_folder(), filter="*.svg") caption="Export SVG positive",
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
except TypeError: except TypeError:
filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export SVG positive") filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export SVG positive")
filename = str(filename) filename = str(filename)
if str(filename) == "": if str(filename) == "":
self.app.inform.emit("Export SVG positive cancelled.") self.app.inform.emit("[WARNING_NOTCL]Export SVG positive cancelled.")
return return
else: else:
self.app.export_svg_black(name, boxname, filename) self.app.export_svg_black(name, boxname, filename, scale_factor=scale_stroke_width)
else: else:
try: try:
filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export SVG negative", filename, _ = QtWidgets.QFileDialog.getSaveFileName(
directory=self.app.get_last_save_folder(), filter="*.svg") caption="Export SVG negative",
directory=self.app.get_last_save_folder() + '/' + name,
filter="*.svg")
except TypeError: except TypeError:
filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export SVG negative") filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export SVG negative")
filename = str(filename) filename = str(filename)
if str(filename) == "": if str(filename) == "":
self.app.inform.emit("Export SVG negative cancelled.") self.app.inform.emit("[WARNING_NOTCL]Export SVG negative cancelled.")
return return
else: else:
self.app.export_svg_negative(name, boxname, filename, border) self.app.export_svg_negative(name, boxname, filename, border, scale_factor=scale_stroke_width)
def reset_fields(self): def reset_fields(self):
self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

View File

@ -124,6 +124,17 @@ class ToolImage(FlatCAMTool):
## Signals ## Signals
self.import_button.clicked.connect(self.on_file_importimage) self.import_button.clicked.connect(self.on_file_importimage)
def run(self):
self.app.report_usage("ToolImage()")
FlatCAMTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "Image Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, **kwargs)
def set_tool_ui(self):
## Initialize form ## Initialize form
self.dpi_entry.set_value(96) self.dpi_entry.set_value(96)
self.image_type.set_value('black') self.image_type.set_value('black')
@ -132,10 +143,6 @@ class ToolImage(FlatCAMTool):
self.mask_g_entry.set_value(250) self.mask_g_entry.set_value(250)
self.mask_b_entry.set_value(250) self.mask_b_entry.set_value(250)
def run(self):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Image Tool")
def on_file_importimage(self): def on_file_importimage(self):
""" """
Callback for menu item File->Import IMAGE. Callback for menu item File->Import IMAGE.

View File

@ -152,11 +152,19 @@ class Measurement(FlatCAMTool):
self.measure_btn.clicked.connect(self.toggle) self.measure_btn.clicked.connect(self.toggle)
def run(self): def run(self):
self.app.report_usage("ToolMeasurement()")
if self.app.tool_tab_locked is True: if self.app.tool_tab_locked is True:
return return
self.toggle() self.toggle()
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "Meas. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='CTRL+M', **kwargs)
def set_tool_ui(self):
# Remove anything else in the GUI # Remove anything else in the GUI
self.app.ui.tool_scroll_area.takeWidget() self.app.ui.tool_scroll_area.takeWidget()
@ -167,21 +175,6 @@ class Measurement(FlatCAMTool):
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().lower() self.units = self.app.general_options_form.general_app_group.units_radio.get_value().lower()
self.show() self.show()
self.app.ui.notebook.setTabText(2, "Meas. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='CTRL+M', **kwargs)
def on_key_release_meas(self, event):
if event.key == 'escape':
# abort the measurement action
self.toggle()
return
if event.key == 'G':
# toggle grid status
self.app.ui.grid_snap_btn.trigger()
return
def toggle(self): def toggle(self):
# the self.active var is doing the 'toggle' # the self.active var is doing the 'toggle'
@ -264,6 +257,17 @@ class Measurement(FlatCAMTool):
self.app.inform.emit("MEASURING: Click on the Start point ...") self.app.inform.emit("MEASURING: Click on the Start point ...")
def on_key_release_meas(self, event):
if event.key == 'escape':
# abort the measurement action
self.toggle()
return
if event.key == 'G':
# toggle grid status
self.app.ui.grid_snap_btn.trigger()
return
def on_click_meas(self, event): def on_click_meas(self, event):
# mouse click will be accepted only if the left button is clicked # mouse click will be accepted only if the left button is clicked
# this is necessary because right mouse click and middle mouse click # this is necessary because right mouse click and middle mouse click

View File

@ -34,10 +34,44 @@ class ToolMove(FlatCAMTool):
FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs)
def run(self): def run(self):
self.app.report_usage("ToolMove()")
if self.app.tool_tab_locked is True: if self.app.tool_tab_locked is True:
return return
self.toggle() self.toggle()
def toggle(self):
if self.isVisible():
self.setVisible(False)
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click)
self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press)
self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
self.clicked_move = 0
# signal that there is no command active
self.app.command_active = None
# delete the selection box
self.delete_shape()
return
else:
self.setVisible(True)
# signal that there is a command active and it is 'Move'
self.app.command_active = "Move"
if self.app.collection.get_selected():
self.app.inform.emit("MOVE: Click on the Start point ...")
# draw the selection box
self.draw_sel_bbox()
else:
self.setVisible(False)
# signal that there is no command active
self.app.command_active = None
self.app.inform.emit("[WARNING_NOTCL]MOVE action cancelled. No object(s) to move.")
def on_left_click(self, event): def on_left_click(self, event):
# mouse click will be accepted only if the left button is clicked # mouse click will be accepted only if the left button is clicked
# this is necessary because right mouse click and middle mouse click # this is necessary because right mouse click and middle mouse click
@ -83,7 +117,7 @@ class ToolMove(FlatCAMTool):
try: try:
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl] No object(s) selected.") self.app.inform.emit("[WARNING_NOTCL] No object(s) selected.")
return "fail" return "fail"
else: else:
for sel_obj in obj_list: for sel_obj in obj_list:
@ -99,7 +133,7 @@ class ToolMove(FlatCAMTool):
# self.app.collection.set_active(sel_obj.options['name']) # self.app.collection.set_active(sel_obj.options['name'])
except Exception as e: except Exception as e:
proc.done() proc.done()
self.app.inform.emit('[error_notcl] ' self.app.inform.emit('[ERROR_NOTCL] '
'ToolMove.on_left_click() --> %s' % str(e)) 'ToolMove.on_left_click() --> %s' % str(e))
return "fail" return "fail"
proc.done() proc.done()
@ -114,7 +148,7 @@ class ToolMove(FlatCAMTool):
return return
except TypeError: except TypeError:
self.app.inform.emit('[error_notcl] ' self.app.inform.emit('[ERROR_NOTCL] '
'ToolMove.on_left_click() --> Error when mouse left click.') 'ToolMove.on_left_click() --> Error when mouse left click.')
return return
@ -142,42 +176,10 @@ class ToolMove(FlatCAMTool):
def on_key_press(self, event): def on_key_press(self, event):
if event.key == 'escape': if event.key == 'escape':
# abort the move action # abort the move action
self.app.inform.emit("[warning_notcl]Move action cancelled.") self.app.inform.emit("[WARNING_NOTCL]Move action cancelled.")
self.toggle() self.toggle()
return return
def toggle(self):
if self.isVisible():
self.setVisible(False)
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click)
self.app.plotcanvas.vis_disconnect('key_release', self.on_key_press)
self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
self.clicked_move = 0
# signal that there is no command active
self.app.command_active = None
# delete the selection box
self.delete_shape()
return
else:
self.setVisible(True)
# signal that there is a command active and it is 'Move'
self.app.command_active = "Move"
if self.app.collection.get_selected():
self.app.inform.emit("MOVE: Click on the Start point ...")
# draw the selection box
self.draw_sel_bbox()
else:
self.setVisible(False)
# signal that there is no command active
self.app.command_active = None
self.app.inform.emit("[warning_notcl]MOVE action cancelled. No object(s) to move.")
def draw_sel_bbox(self): def draw_sel_bbox(self):
xminlist = [] xminlist = []
yminlist = [] yminlist = []
@ -186,7 +188,7 @@ class ToolMove(FlatCAMTool):
obj_list = self.app.collection.get_selected() obj_list = self.app.collection.get_selected()
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl]Object(s) not selected") self.app.inform.emit("[WARNING_NOTCL]Object(s) not selected")
self.toggle() self.toggle()
else: else:
# if we have an object selected then we can safely activate the mouse events # if we have an object selected then we can safely activate the mouse events

View File

@ -35,6 +35,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.object_combo.setModel(self.app.collection) self.object_combo.setModel(self.app.collection)
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.object_combo.setCurrentIndex(1) self.object_combo.setCurrentIndex(1)
self.object_label = QtWidgets.QLabel("Gerber:") self.object_label = QtWidgets.QLabel("Gerber:")
self.object_label.setToolTip( self.object_label.setToolTip(
"Gerber object to be cleared of excess copper. " "Gerber object to be cleared of excess copper. "
@ -97,7 +98,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.addtool_entry_lbl.setToolTip( self.addtool_entry_lbl.setToolTip(
"Diameter for the new tool to add in the Tool Table" "Diameter for the new tool to add in the Tool Table"
) )
self.addtool_entry = FloatEntry() self.addtool_entry = FCEntry()
# hlay.addWidget(self.addtool_label) # hlay.addWidget(self.addtool_label)
# hlay.addStretch() # hlay.addStretch()
@ -151,7 +152,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
"due of too many paths." "due of too many paths."
) )
grid3.addWidget(nccoverlabel, 1, 0) grid3.addWidget(nccoverlabel, 1, 0)
self.ncc_overlap_entry = FloatEntry() self.ncc_overlap_entry = FCEntry()
grid3.addWidget(self.ncc_overlap_entry, 1, 1) grid3.addWidget(self.ncc_overlap_entry, 1, 1)
nccmarginlabel = QtWidgets.QLabel('Margin:') nccmarginlabel = QtWidgets.QLabel('Margin:')
@ -159,7 +160,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
"Bounding box margin." "Bounding box margin."
) )
grid3.addWidget(nccmarginlabel, 2, 0) grid3.addWidget(nccmarginlabel, 2, 0)
self.ncc_margin_entry = FloatEntry() self.ncc_margin_entry = FCEntry()
grid3.addWidget(self.ncc_margin_entry, 2, 1) grid3.addWidget(self.ncc_margin_entry, 2, 1)
# Method # Method
@ -237,13 +238,16 @@ class NonCopperClear(FlatCAMTool, Gerber):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
def run(self): def run(self):
self.app.report_usage("ToolNonCopperClear()")
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.tools_frame.show() self.set_tool_ui()
self.set_ui()
self.build_ui() self.build_ui()
self.app.ui.notebook.setTabText(2, "NCC Tool") self.app.ui.notebook.setTabText(2, "NCC Tool")
def set_ui(self): def set_tool_ui(self):
self.tools_frame.show()
self.ncc_overlap_entry.set_value(self.app.defaults["tools_nccoverlap"]) self.ncc_overlap_entry.set_value(self.app.defaults["tools_nccoverlap"])
self.ncc_margin_entry.set_value(self.app.defaults["tools_nccmargin"]) self.ncc_margin_entry.set_value(self.app.defaults["tools_nccmargin"])
self.ncc_method_radio.set_value(self.app.defaults["tools_nccmethod"]) self.ncc_method_radio.set_value(self.app.defaults["tools_nccmethod"])
@ -408,7 +412,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.tools_table.setMinimumHeight(self.tools_table.getHeight()) self.tools_table.setMinimumHeight(self.tools_table.getHeight())
self.tools_table.setMaximumHeight(self.tools_table.getHeight()) self.tools_table.setMaximumHeight(self.tools_table.getHeight())
self.app.report_usage("gerber_on_ncc_button")
self.ui_connect() self.ui_connect()
def ui_connect(self): def ui_connect(self):
@ -428,10 +431,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
if dia: if dia:
tool_dia = dia tool_dia = dia
else: else:
tool_dia = self.addtool_entry.get_value() try:
tool_dia = float(self.addtool_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
if tool_dia is None: if tool_dia is None:
self.build_ui() self.build_ui()
self.app.inform.emit("[warning_notcl] Please enter a tool diameter to add, in Float format.") self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
return return
# construct a list of all 'tooluid' in the self.tools # construct a list of all 'tooluid' in the self.tools
@ -455,7 +467,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
if float('%.4f' % tool_dia) in tool_dias: if float('%.4f' % tool_dia) in tool_dias:
if muted is None: if muted is None:
self.app.inform.emit("[warning_notcl]Adding tool cancelled. Tool already in Tool Table.") self.app.inform.emit("[WARNING_NOTCL]Adding tool cancelled. Tool already in Tool Table.")
self.tools_table.itemChanged.connect(self.on_tool_edit) self.tools_table.itemChanged.connect(self.on_tool_edit)
return return
else: else:
@ -485,7 +497,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
tool_dias.append(float('%.4f' % v[tool_v])) tool_dias.append(float('%.4f' % v[tool_v]))
for row in range(self.tools_table.rowCount()): for row in range(self.tools_table.rowCount()):
try:
new_tool_dia = float(self.tools_table.item(row, 1).text()) new_tool_dia = float(self.tools_table.item(row, 1).text())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
tooluid = int(self.tools_table.item(row, 3).text()) tooluid = int(self.tools_table.item(row, 3).text())
# identify the tool that was edited and get it's tooluid # identify the tool that was edited and get it's tooluid
@ -502,7 +525,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
break break
restore_dia_item = self.tools_table.item(row, 1) restore_dia_item = self.tools_table.item(row, 1)
restore_dia_item.setText(str(old_tool_dia)) restore_dia_item.setText(str(old_tool_dia))
self.app.inform.emit("[warning_notcl] Edit cancelled. New diameter value is already in the Tool Table.") self.app.inform.emit("[WARNING_NOTCL] Edit cancelled. New diameter value is already in the Tool Table.")
self.build_ui() self.build_ui()
def on_tool_delete(self, rows_to_delete=None, all=None): def on_tool_delete(self, rows_to_delete=None, all=None):
@ -541,7 +564,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.ncc_tools.pop(t, None) self.ncc_tools.pop(t, None)
except AttributeError: except AttributeError:
self.app.inform.emit("[warning_notcl]Delete failed. Select a tool to delete.") self.app.inform.emit("[WARNING_NOTCL]Delete failed. Select a tool to delete.")
return return
except Exception as e: except Exception as e:
log.debug(str(e)) log.debug(str(e))
@ -551,10 +574,28 @@ class NonCopperClear(FlatCAMTool, Gerber):
def on_ncc(self): def on_ncc(self):
over = self.ncc_overlap_entry.get_value() try:
over = float(self.ncc_overlap_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
over = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
over = over if over else self.app.defaults["tools_nccoverlap"] over = over if over else self.app.defaults["tools_nccoverlap"]
margin = self.ncc_margin_entry.get_value() try:
margin = float(self.ncc_margin_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
margin = float(self.ncc_margin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
margin = margin if margin else self.app.defaults["tools_nccmargin"] margin = margin if margin else self.app.defaults["tools_nccmargin"]
connect = self.ncc_connect_cb.get_value() connect = self.ncc_connect_cb.get_value()
@ -574,7 +615,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
try: try:
self.ncc_obj = self.app.collection.get_by_name(self.obj_name) self.ncc_obj = self.app.collection.get_by_name(self.obj_name)
except: except:
self.app.inform.emit("[error_notcl]Could not retrieve object: %s" % self.obj_name) self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % self.obj_name)
return "Could not retrieve object: %s" % self.obj_name return "Could not retrieve object: %s" % self.obj_name
@ -582,7 +623,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
try: try:
bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre) bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre)
except AttributeError: except AttributeError:
self.app.inform.emit("[error_notcl]No Gerber file available.") self.app.inform.emit("[ERROR_NOTCL]No Gerber file available.")
return return
# calculate the empty area by substracting the solid_geometry from the object bounding box geometry # calculate the empty area by substracting the solid_geometry from the object bounding box geometry
@ -707,14 +748,14 @@ class NonCopperClear(FlatCAMTool, Gerber):
app_obj.new_object("geometry", name, initialize) app_obj.new_object("geometry", name, initialize)
except Exception as e: except Exception as e:
proc.done() proc.done()
self.app.inform.emit('[error_notcl] NCCTool.clear_non_copper() --> %s' % str(e)) self.app.inform.emit('[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s' % str(e))
return return
proc.done() proc.done()
if app_obj.poly_not_cleared is False: if app_obj.poly_not_cleared is False:
self.app.inform.emit('[success] NCC Tool finished.') self.app.inform.emit('[success] NCC Tool finished.')
else: else:
self.app.inform.emit('[warning_notcl] NCC Tool finished but some PCB features could not be cleared. ' self.app.inform.emit('[WARNING_NOTCL] NCC Tool finished but some PCB features could not be cleared. '
'Check the result.') 'Check the result.')
# reset the variable for next use # reset the variable for next use
app_obj.poly_not_cleared = False app_obj.poly_not_cleared = False
@ -858,7 +899,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
app_obj.new_object("geometry", name, initialize_rm) app_obj.new_object("geometry", name, initialize_rm)
except Exception as e: except Exception as e:
proc.done() proc.done()
self.app.inform.emit('[error_notcl] NCCTool.clear_non_copper_rest() --> %s' % str(e)) self.app.inform.emit('[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s' % str(e))
return return
if app_obj.poly_not_cleared is True: if app_obj.poly_not_cleared is True:
@ -866,7 +907,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
# focus on Selected Tab # focus on Selected Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
else: else:
self.app.inform.emit('[error_notcl] NCC Tool finished but could not clear the object ' self.app.inform.emit('[ERROR_NOTCL] NCC Tool finished but could not clear the object '
'with current settings.') 'with current settings.')
# focus on Project Tab # focus on Project Tab
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
@ -882,3 +923,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
# Background # Background
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def reset_fields(self):
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))

View File

@ -33,6 +33,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.object_combo.setModel(self.app.collection) self.object_combo.setModel(self.app.collection)
self.object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) self.object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
self.object_combo.setCurrentIndex(1) self.object_combo.setCurrentIndex(1)
self.object_label = QtWidgets.QLabel("Geometry:") self.object_label = QtWidgets.QLabel("Geometry:")
self.object_label.setToolTip( self.object_label.setToolTip(
"Geometry object to be painted. " "Geometry object to be painted. "
@ -94,7 +95,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.addtool_entry_lbl.setToolTip( self.addtool_entry_lbl.setToolTip(
"Diameter for the new tool." "Diameter for the new tool."
) )
self.addtool_entry = FloatEntry() self.addtool_entry = FCEntry()
# hlay.addWidget(self.addtool_label) # hlay.addWidget(self.addtool_label)
# hlay.addStretch() # hlay.addStretch()
@ -146,7 +147,7 @@ class ToolPaint(FlatCAMTool, Gerber):
"due of too many paths." "due of too many paths."
) )
grid3.addWidget(ovlabel, 1, 0) grid3.addWidget(ovlabel, 1, 0)
self.paintoverlap_entry = LengthEntry() self.paintoverlap_entry = FCEntry()
grid3.addWidget(self.paintoverlap_entry, 1, 1) grid3.addWidget(self.paintoverlap_entry, 1, 1)
# Margin # Margin
@ -157,7 +158,7 @@ class ToolPaint(FlatCAMTool, Gerber):
"be painted." "be painted."
) )
grid3.addWidget(marginlabel, 2, 0) grid3.addWidget(marginlabel, 2, 0)
self.paintmargin_entry = LengthEntry() self.paintmargin_entry = FCEntry()
grid3.addWidget(self.paintmargin_entry, 2, 1) grid3.addWidget(self.paintmargin_entry, 2, 1)
# Method # Method
@ -294,9 +295,10 @@ class ToolPaint(FlatCAMTool, Gerber):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs)
def run(self): def run(self):
self.app.report_usage("ToolPaint()")
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.tools_frame.show() self.set_tool_ui()
self.set_ui()
self.app.ui.notebook.setTabText(2, "Paint Tool") self.app.ui.notebook.setTabText(2, "Paint Tool")
def on_radio_selection(self): def on_radio_selection(self):
@ -320,7 +322,10 @@ class ToolPaint(FlatCAMTool, Gerber):
self.deltool_btn.setDisabled(False) self.deltool_btn.setDisabled(False)
self.tools_table.setContextMenuPolicy(Qt.ActionsContextMenu) self.tools_table.setContextMenuPolicy(Qt.ActionsContextMenu)
def set_ui(self): def set_tool_ui(self):
self.tools_frame.show()
self.reset_fields()
## Init the GUI interface ## Init the GUI interface
self.paintmargin_entry.set_value(self.default_data["paintmargin"]) self.paintmargin_entry.set_value(self.default_data["paintmargin"])
self.paintmethod_combo.set_value(self.default_data["paintmethod"]) self.paintmethod_combo.set_value(self.default_data["paintmethod"])
@ -484,10 +489,20 @@ class ToolPaint(FlatCAMTool, Gerber):
if dia: if dia:
tool_dia = dia tool_dia = dia
else: else:
tool_dia = self.addtool_entry.get_value() try:
tool_dia = float(self.addtool_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
if tool_dia is None: if tool_dia is None:
self.build_ui() self.build_ui()
self.app.inform.emit("[warning_notcl] Please enter a tool diameter to add, in Float format.") self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
return return
# construct a list of all 'tooluid' in the self.tools # construct a list of all 'tooluid' in the self.tools
@ -511,7 +526,7 @@ class ToolPaint(FlatCAMTool, Gerber):
if float('%.4f' % tool_dia) in tool_dias: if float('%.4f' % tool_dia) in tool_dias:
if muted is None: if muted is None:
self.app.inform.emit("[warning_notcl]Adding tool cancelled. Tool already in Tool Table.") self.app.inform.emit("[WARNING_NOTCL]Adding tool cancelled. Tool already in Tool Table.")
self.tools_table.itemChanged.connect(self.on_tool_edit) self.tools_table.itemChanged.connect(self.on_tool_edit)
return return
else: else:
@ -544,7 +559,16 @@ class ToolPaint(FlatCAMTool, Gerber):
tool_dias.append(float('%.4f' % v[tool_v])) tool_dias.append(float('%.4f' % v[tool_v]))
for row in range(self.tools_table.rowCount()): for row in range(self.tools_table.rowCount()):
try:
new_tool_dia = float(self.tools_table.item(row, 1).text()) new_tool_dia = float(self.tools_table.item(row, 1).text())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
new_tool_dia = float(self.tools_table.item(row, 1).text().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
tooluid = int(self.tools_table.item(row, 3).text()) tooluid = int(self.tools_table.item(row, 3).text())
# identify the tool that was edited and get it's tooluid # identify the tool that was edited and get it's tooluid
@ -561,7 +585,7 @@ class ToolPaint(FlatCAMTool, Gerber):
break break
restore_dia_item = self.tools_table.item(row, 1) restore_dia_item = self.tools_table.item(row, 1)
restore_dia_item.setText(str(old_tool_dia)) restore_dia_item.setText(str(old_tool_dia))
self.app.inform.emit("[warning_notcl] Edit cancelled. New diameter value is already in the Tool Table.") self.app.inform.emit("[WARNING_NOTCL] Edit cancelled. New diameter value is already in the Tool Table.")
self.build_ui() self.build_ui()
# def on_tool_copy(self, all=None): # def on_tool_copy(self, all=None):
@ -594,7 +618,7 @@ class ToolPaint(FlatCAMTool, Gerber):
# print("COPIED", self.paint_tools[td]) # print("COPIED", self.paint_tools[td])
# self.build_ui() # self.build_ui()
# except AttributeError: # except AttributeError:
# self.app.inform.emit("[warning_notcl]Failed. Select a tool to copy.") # self.app.inform.emit("[WARNING_NOTCL]Failed. Select a tool to copy.")
# self.build_ui() # self.build_ui()
# return # return
# except Exception as e: # except Exception as e:
@ -602,7 +626,7 @@ class ToolPaint(FlatCAMTool, Gerber):
# # deselect the table # # deselect the table
# # self.ui.geo_tools_table.clearSelection() # # self.ui.geo_tools_table.clearSelection()
# else: # else:
# self.app.inform.emit("[warning_notcl]Failed. Select a tool to copy.") # self.app.inform.emit("[WARNING_NOTCL]Failed. Select a tool to copy.")
# self.build_ui() # self.build_ui()
# return # return
# else: # else:
@ -658,7 +682,7 @@ class ToolPaint(FlatCAMTool, Gerber):
self.paint_tools.pop(t, None) self.paint_tools.pop(t, None)
except AttributeError: except AttributeError:
self.app.inform.emit("[warning_notcl]Delete failed. Select a tool to delete.") self.app.inform.emit("[WARNING_NOTCL]Delete failed. Select a tool to delete.")
return return
except Exception as e: except Exception as e:
log.debug(str(e)) log.debug(str(e))
@ -669,9 +693,18 @@ class ToolPaint(FlatCAMTool, Gerber):
def on_paint_button_click(self): def on_paint_button_click(self):
self.app.report_usage("geometry_on_paint_button") self.app.report_usage("geometry_on_paint_button")
self.app.inform.emit("[warning_notcl]Click inside the desired polygon.") self.app.inform.emit("[WARNING_NOTCL]Click inside the desired polygon.")
try:
overlap = float(self.paintoverlap_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
overlap = self.paintoverlap_entry.get_value()
connect = self.pathconnect_cb.get_value() connect = self.pathconnect_cb.get_value()
contour = self.paintcontour_cb.get_value() contour = self.paintcontour_cb.get_value()
select_method = self.selectmethod_combo.get_value() select_method = self.selectmethod_combo.get_value()
@ -682,11 +715,11 @@ class ToolPaint(FlatCAMTool, Gerber):
try: try:
self.paint_obj = self.app.collection.get_by_name(str(self.obj_name)) self.paint_obj = self.app.collection.get_by_name(str(self.obj_name))
except: except:
self.app.inform.emit("[error_notcl]Could not retrieve object: %s" % self.obj_name) self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % self.obj_name)
return return
if self.paint_obj is None: if self.paint_obj is None:
self.app.inform.emit("[error_notcl]Object not found: %s" % self.paint_obj) self.app.inform.emit("[ERROR_NOTCL]Object not found: %s" % self.paint_obj)
return return
o_name = '%s_multitool_paint' % (self.obj_name) o_name = '%s_multitool_paint' % (self.obj_name)
@ -699,7 +732,7 @@ class ToolPaint(FlatCAMTool, Gerber):
contour=contour) contour=contour)
if select_method == "single": if select_method == "single":
self.app.inform.emit("[warning_notcl]Click inside the desired polygon.") self.app.inform.emit("[WARNING_NOTCL]Click inside the desired polygon.")
# use the first tool in the tool table; get the diameter # use the first tool in the tool table; get the diameter
tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text())) tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
@ -742,12 +775,22 @@ class ToolPaint(FlatCAMTool, Gerber):
# poly = find_polygon(self.solid_geometry, inside_pt) # poly = find_polygon(self.solid_geometry, inside_pt)
poly = obj.find_polygon(inside_pt) poly = obj.find_polygon(inside_pt)
paint_method = self.paintmethod_combo.get_value() paint_method = self.paintmethod_combo.get_value()
paint_margin = self.paintmargin_entry.get_value()
try:
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
# No polygon? # No polygon?
if poly is None: if poly is None:
self.app.log.warning('No polygon found.') self.app.log.warning('No polygon found.')
self.app.inform.emit('[warning] No polygon found.') self.app.inform.emit('[WARNING] No polygon found.')
return return
proc = self.app.proc_container.new("Painting polygon.") proc = self.app.proc_container.new("Painting polygon.")
@ -792,7 +835,7 @@ class ToolPaint(FlatCAMTool, Gerber):
geo_obj.solid_geometry += list(cp.get_objects()) geo_obj.solid_geometry += list(cp.get_objects())
return cp return cp
else: else:
self.app.inform.emit('[error_notcl] Geometry could not be painted completely') self.app.inform.emit('[ERROR_NOTCL] Geometry could not be painted completely')
return None return None
geo_obj.solid_geometry = [] geo_obj.solid_geometry = []
@ -807,7 +850,7 @@ class ToolPaint(FlatCAMTool, Gerber):
except Exception as e: except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e)) log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit( self.app.inform.emit(
"[error] Could not do Paint. Try a different combination of parameters. " "[ERROR] Could not do Paint. Try a different combination of parameters. "
"Or a different strategy of paint\n%s" % str(e)) "Or a different strategy of paint\n%s" % str(e))
return return
@ -838,7 +881,7 @@ class ToolPaint(FlatCAMTool, Gerber):
# self.app.inform.emit("[success] Paint single polygon Done") # self.app.inform.emit("[success] Paint single polygon Done")
# else: # else:
# print("[WARNING] Paint single polygon done with errors") # print("[WARNING] Paint single polygon done with errors")
# self.app.inform.emit("[warning] Paint single polygon done with errors. " # self.app.inform.emit("[WARNING] Paint single polygon done with errors. "
# "%d area(s) could not be painted.\n" # "%d area(s) could not be painted.\n"
# "Use different paint parameters or edit the paint geometry and correct" # "Use different paint parameters or edit the paint geometry and correct"
# "the issue." # "the issue."
@ -849,7 +892,7 @@ class ToolPaint(FlatCAMTool, Gerber):
app_obj.new_object("geometry", name, gen_paintarea) app_obj.new_object("geometry", name, gen_paintarea)
except Exception as e: except Exception as e:
proc.done() proc.done()
self.app.inform.emit('[error_notcl] PaintTool.paint_poly() --> %s' % str(e)) self.app.inform.emit('[ERROR_NOTCL] PaintTool.paint_poly() --> %s' % str(e))
return return
proc.done() proc.done()
# focus on Selected Tab # focus on Selected Tab
@ -876,10 +919,19 @@ class ToolPaint(FlatCAMTool, Gerber):
:return: :return:
""" """
paint_method = self.paintmethod_combo.get_value() paint_method = self.paintmethod_combo.get_value()
paint_margin = self.paintmargin_entry.get_value()
try:
paint_margin = float(self.paintmargin_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
proc = self.app.proc_container.new("Painting polygon.") proc = self.app.proc_container.new("Painting polygon.")
name = outname if outname else self.obj_name + "_paint" name = outname if outname else self.obj_name + "_paint"
over = overlap over = overlap
conn = connect conn = connect
@ -984,7 +1036,7 @@ class ToolPaint(FlatCAMTool, Gerber):
except Exception as e: except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e)) log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit( self.app.inform.emit(
"[error] Could not do Paint All. Try a different combination of parameters. " "[ERROR] Could not do Paint All. Try a different combination of parameters. "
"Or a different Method of paint\n%s" % str(e)) "Or a different Method of paint\n%s" % str(e))
return return
@ -1008,7 +1060,7 @@ class ToolPaint(FlatCAMTool, Gerber):
if geo_obj.tools[tooluid]['solid_geometry']: if geo_obj.tools[tooluid]['solid_geometry']:
has_solid_geo += 1 has_solid_geo += 1
if has_solid_geo == 0: if has_solid_geo == 0:
self.app.inform.emit("[error] There is no Painting Geometry in the file.\n" self.app.inform.emit("[ERROR] There is no Painting Geometry in the file.\n"
"Usually it means that the tool diameter is too big for the painted geometry.\n" "Usually it means that the tool diameter is too big for the painted geometry.\n"
"Change the painting parameters and try again.") "Change the painting parameters and try again.")
return return
@ -1063,7 +1115,7 @@ class ToolPaint(FlatCAMTool, Gerber):
except Exception as e: except Exception as e:
log.debug("Could not Paint the polygons. %s" % str(e)) log.debug("Could not Paint the polygons. %s" % str(e))
self.app.inform.emit( self.app.inform.emit(
"[error] Could not do Paint All. Try a different combination of parameters. " "[ERROR] Could not do Paint All. Try a different combination of parameters. "
"Or a different Method of paint\n%s" % str(e)) "Or a different Method of paint\n%s" % str(e))
return return
@ -1093,7 +1145,7 @@ class ToolPaint(FlatCAMTool, Gerber):
if geo_obj.tools[tooluid]['solid_geometry']: if geo_obj.tools[tooluid]['solid_geometry']:
has_solid_geo += 1 has_solid_geo += 1
if has_solid_geo == 0: if has_solid_geo == 0:
self.app.inform.emit("[error_notcl] There is no Painting Geometry in the file.\n" self.app.inform.emit("[ERROR_NOTCL] There is no Painting Geometry in the file.\n"
"Usually it means that the tool diameter is too big for the painted geometry.\n" "Usually it means that the tool diameter is too big for the painted geometry.\n"
"Change the painting parameters and try again.") "Change the painting parameters and try again.")
return return
@ -1125,3 +1177,6 @@ class ToolPaint(FlatCAMTool, Gerber):
# Background # Background
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def reset_fields(self):
self.object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))

View File

@ -44,6 +44,7 @@ class Panelize(FlatCAMTool):
self.object_combo.setModel(self.app.collection) self.object_combo.setModel(self.app.collection)
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.object_combo.setCurrentIndex(1) self.object_combo.setCurrentIndex(1)
self.object_label = QtWidgets.QLabel("Object:") self.object_label = QtWidgets.QLabel("Object:")
self.object_label.setToolTip( self.object_label.setToolTip(
"Object to be panelized. This means that it will\n" "Object to be panelized. This means that it will\n"
@ -76,6 +77,7 @@ class Panelize(FlatCAMTool):
self.box_combo.setModel(self.app.collection) self.box_combo.setModel(self.app.collection)
self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
self.box_combo.setCurrentIndex(1) self.box_combo.setCurrentIndex(1)
self.box_combo_label = QtWidgets.QLabel("Box Object:") self.box_combo_label = QtWidgets.QLabel("Box Object:")
self.box_combo_label.setToolTip( self.box_combo_label.setToolTip(
"The actual object that is used a container for the\n " "The actual object that is used a container for the\n "
@ -84,8 +86,7 @@ class Panelize(FlatCAMTool):
form_layout.addRow(self.box_combo_label, self.box_combo) form_layout.addRow(self.box_combo_label, self.box_combo)
## Spacing Columns ## Spacing Columns
self.spacing_columns = FloatEntry() self.spacing_columns = FCEntry()
self.spacing_columns.set_value(0.0)
self.spacing_columns_label = QtWidgets.QLabel("Spacing cols:") self.spacing_columns_label = QtWidgets.QLabel("Spacing cols:")
self.spacing_columns_label.setToolTip( self.spacing_columns_label.setToolTip(
"Spacing between columns of the desired panel.\n" "Spacing between columns of the desired panel.\n"
@ -94,8 +95,7 @@ class Panelize(FlatCAMTool):
form_layout.addRow(self.spacing_columns_label, self.spacing_columns) form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
## Spacing Rows ## Spacing Rows
self.spacing_rows = FloatEntry() self.spacing_rows = FCEntry()
self.spacing_rows.set_value(0.0)
self.spacing_rows_label = QtWidgets.QLabel("Spacing rows:") self.spacing_rows_label = QtWidgets.QLabel("Spacing rows:")
self.spacing_rows_label.setToolTip( self.spacing_rows_label.setToolTip(
"Spacing between rows of the desired panel.\n" "Spacing between rows of the desired panel.\n"
@ -104,8 +104,7 @@ class Panelize(FlatCAMTool):
form_layout.addRow(self.spacing_rows_label, self.spacing_rows) form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
## Columns ## Columns
self.columns = IntEntry() self.columns = FCEntry()
self.columns.set_value(1)
self.columns_label = QtWidgets.QLabel("Columns:") self.columns_label = QtWidgets.QLabel("Columns:")
self.columns_label.setToolTip( self.columns_label.setToolTip(
"Number of columns of the desired panel" "Number of columns of the desired panel"
@ -113,8 +112,7 @@ class Panelize(FlatCAMTool):
form_layout.addRow(self.columns_label, self.columns) form_layout.addRow(self.columns_label, self.columns)
## Rows ## Rows
self.rows = IntEntry() self.rows = FCEntry()
self.rows.set_value(1)
self.rows_label = QtWidgets.QLabel("Rows:") self.rows_label = QtWidgets.QLabel("Rows:")
self.rows_label.setToolTip( self.rows_label.setToolTip(
"Number of rows of the desired panel" "Number of rows of the desired panel"
@ -132,8 +130,7 @@ class Panelize(FlatCAMTool):
) )
form_layout.addRow(self.constrain_cb) form_layout.addRow(self.constrain_cb)
self.x_width_entry = FloatEntry() self.x_width_entry = FCEntry()
self.x_width_entry.set_value(0.0)
self.x_width_lbl = QtWidgets.QLabel("Width (DX):") self.x_width_lbl = QtWidgets.QLabel("Width (DX):")
self.x_width_lbl.setToolTip( self.x_width_lbl.setToolTip(
"The width (DX) within which the panel must fit.\n" "The width (DX) within which the panel must fit.\n"
@ -141,8 +138,7 @@ class Panelize(FlatCAMTool):
) )
form_layout.addRow(self.x_width_lbl, self.x_width_entry) form_layout.addRow(self.x_width_lbl, self.x_width_entry)
self.y_height_entry = FloatEntry() self.y_height_entry = FCEntry()
self.y_height_entry.set_value(0.0)
self.y_height_lbl = QtWidgets.QLabel("Height (DY):") self.y_height_lbl = QtWidgets.QLabel("Height (DY):")
self.y_height_lbl.setToolTip( self.y_height_lbl.setToolTip(
"The height (DY)within which the panel must fit.\n" "The height (DY)within which the panel must fit.\n"
@ -183,6 +179,47 @@ class Panelize(FlatCAMTool):
# flag to signal the constrain was activated # flag to signal the constrain was activated
self.constrain_flag = False self.constrain_flag = False
def run(self):
self.app.report_usage("ToolPanelize()")
FlatCAMTool.run(self)
self.set_tool_ui()
self.app.ui.notebook.setTabText(2, "Panel. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+Z', **kwargs)
def set_tool_ui(self):
self.reset_fields()
sp_c = self.app.defaults["tools_panelize_spacing_columns"] if \
self.app.defaults["tools_panelize_spacing_columns"] else 0.0
self.spacing_columns.set_value(float(sp_c))
sp_r = self.app.defaults["tools_panelize_spacing_rows"] if \
self.app.defaults["tools_panelize_spacing_rows"] else 0.0
self.spacing_rows.set_value(float(sp_r))
rr = self.app.defaults["tools_panelize_rows"] if \
self.app.defaults["tools_panelize_rows"] else 0.0
self.rows.set_value(int(rr))
cc = self.app.defaults["tools_panelize_columns"] if \
self.app.defaults["tools_panelize_columns"] else 0.0
self.columns.set_value(int(cc))
c_cb = self.app.defaults["tools_panelize_constrain"] if \
self.app.defaults["tools_panelize_constrain"] else False
self.constrain_cb.set_value(c_cb)
x_w = self.app.defaults["tools_panelize_constrainx"] if \
self.app.defaults["tools_panelize_constrainx"] else 0.0
self.x_width_entry.set_value(float(x_w))
y_w = self.app.defaults["tools_panelize_constrainy"] if \
self.app.defaults["tools_panelize_constrainy"] else 0.0
self.y_height_entry.set_value(float(y_w))
def on_type_obj_index_changed(self): def on_type_obj_index_changed(self):
obj_type = self.type_obj_combo.currentIndex() obj_type = self.type_obj_combo.currentIndex()
self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex())) self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
@ -193,13 +230,6 @@ class Panelize(FlatCAMTool):
self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex())) self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
self.box_combo.setCurrentIndex(0) self.box_combo.setCurrentIndex(0)
def run(self):
FlatCAMTool.run(self)
self.app.ui.notebook.setTabText(2, "Panel. Tool")
def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+Z', **kwargs)
def on_panelize(self): def on_panelize(self):
name = self.object_combo.currentText() name = self.object_combo.currentText()
@ -207,13 +237,13 @@ class Panelize(FlatCAMTool):
try: try:
obj = self.app.collection.get_by_name(str(name)) obj = self.app.collection.get_by_name(str(name))
except: except:
self.app.inform.emit("[error_notcl]Could not retrieve object: %s" % name) self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % name)
return "Could not retrieve object: %s" % name return "Could not retrieve object: %s" % name
panel_obj = obj panel_obj = obj
if panel_obj is None: if panel_obj is None:
self.app.inform.emit("[error_notcl]Object not found: %s" % panel_obj) self.app.inform.emit("[ERROR_NOTCL]Object not found: %s" % panel_obj)
return "Object not found: %s" % panel_obj return "Object not found: %s" % panel_obj
boxname = self.box_combo.currentText() boxname = self.box_combo.currentText()
@ -221,32 +251,89 @@ class Panelize(FlatCAMTool):
try: try:
box = self.app.collection.get_by_name(boxname) box = self.app.collection.get_by_name(boxname)
except: except:
self.app.inform.emit("[error_notcl]Could not retrieve object: %s" % boxname) self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % boxname)
return "Could not retrieve object: %s" % boxname return "Could not retrieve object: %s" % boxname
if box is None: if box is None:
self.app.inform.emit("[warning]No object Box. Using instead %s" % panel_obj) self.app.inform.emit("[WARNING]No object Box. Using instead %s" % panel_obj)
box = panel_obj box = panel_obj
self.outname = name + '_panelized' self.outname = name + '_panelized'
spacing_columns = self.spacing_columns.get_value() try:
spacing_columns = float(self.spacing_columns.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
spacing_columns = float(self.spacing_columns.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
spacing_columns = spacing_columns if spacing_columns is not None else 0 spacing_columns = spacing_columns if spacing_columns is not None else 0
spacing_rows = self.spacing_rows.get_value() try:
spacing_rows = float(self.spacing_rows.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
spacing_rows = float(self.spacing_rows.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
spacing_rows = spacing_rows if spacing_rows is not None else 0 spacing_rows = spacing_rows if spacing_rows is not None else 0
rows = self.rows.get_value() try:
rows = int(self.rows.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
rows = float(self.rows.get_value().replace(',', '.'))
rows = int(rows)
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
rows = rows if rows is not None else 1 rows = rows if rows is not None else 1
columns = self.columns.get_value() try:
columns = int(self.columns.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
columns = float(self.columns.get_value().replace(',', '.'))
columns = int(columns)
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
columns = columns if columns is not None else 1 columns = columns if columns is not None else 1
constrain_dx = self.x_width_entry.get_value() try:
constrain_dy = self.y_height_entry.get_value() constrain_dx = float(self.x_width_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
constrain_dx = float(self.x_width_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
constrain_dy = float(self.y_height_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
constrain_dy = float(self.y_height_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
if 0 in {columns, rows}: if 0 in {columns, rows}:
self.app.inform.emit("[error_notcl]Columns or Rows are zero value. Change them to a positive integer.") 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." return "Columns or Rows are zero value. Change them to a positive integer."
xmin, ymin, xmax, ymax = box.bounds() xmin, ymin, xmax, ymax = box.bounds()
@ -342,7 +429,7 @@ class Panelize(FlatCAMTool):
# self.app.new_object("geometry", self.outname, job_init_geometry, plot=True, autoselected=True) # self.app.new_object("geometry", self.outname, job_init_geometry, plot=True, autoselected=True)
# #
# else: # else:
# self.app.inform.emit("[error_notcl] Obj is None") # self.app.inform.emit("[ERROR_NOTCL] Obj is None")
# return "ERROR: Obj is None" # return "ERROR: Obj is None"
# panelize() # panelize()
@ -455,7 +542,7 @@ class Panelize(FlatCAMTool):
self.app.inform.emit("[success]Panel done...") self.app.inform.emit("[success]Panel done...")
else: else:
self.constrain_flag = False self.constrain_flag = False
self.app.inform.emit("[warning] Too big for the constrain area. Final panel has %s columns and %s rows" % self.app.inform.emit("[WARNING] Too big for the constrain area. Final panel has %s columns and %s rows" %
(columns, rows)) (columns, rows))
proc = self.app.proc_container.new("Generating panel ... Please wait.") proc = self.app.proc_container.new("Generating panel ... Please wait.")

View File

@ -42,24 +42,26 @@ class Properties(FlatCAMTool):
self.vlay.setStretch(0,0) self.vlay.setStretch(0,0)
def run(self): def run(self):
self.app.report_usage("ToolProperties()")
if self.app.tool_tab_locked is True: if self.app.tool_tab_locked is True:
return return
self.set_tool_ui()
# this reset the TreeWidget
self.treeWidget.clear()
self.properties_frame.show()
FlatCAMTool.run(self) FlatCAMTool.run(self)
self.properties() self.properties()
def install(self, icon=None, separator=None, **kwargs): def install(self, icon=None, separator=None, **kwargs):
FlatCAMTool.install(self, icon, separator, shortcut='P', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='P', **kwargs)
def set_tool_ui(self):
# this reset the TreeWidget
self.treeWidget.clear()
self.properties_frame.show()
def properties(self): def properties(self):
obj_list = self.app.collection.get_selected() obj_list = self.app.collection.get_selected()
if not obj_list: if not obj_list:
self.app.inform.emit("[error_notcl] Properties Tool was not displayed. No object selected.") self.app.inform.emit("[ERROR_NOTCL] Properties Tool was not displayed. No object selected.")
self.app.ui.notebook.setTabText(2, "Tools") self.app.ui.notebook.setTabText(2, "Tools")
self.properties_frame.hide() self.properties_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)

View File

@ -234,7 +234,7 @@ class TermWidget(QWidget):
""" """
Convert text to HTML for inserting it to browser Convert text to HTML for inserting it to browser
""" """
assert style in ('in', 'out', 'err') assert style in ('in', 'out', 'err', 'warning', 'success')
text = html.escape(text) text = html.escape(text)
text = text.replace('\n', '<br/>') text = text.replace('\n', '<br/>')
@ -243,6 +243,10 @@ class TermWidget(QWidget):
text = '<span style="font-weight: bold;">%s</span>' % text text = '<span style="font-weight: bold;">%s</span>' % text
elif style == 'err': elif style == 'err':
text = '<span style="font-weight: bold; color: red;">%s</span>' % text text = '<span style="font-weight: bold; color: red;">%s</span>' % text
elif style == 'warning':
text = '<span style="font-weight: bold; color: rgb(244, 182, 66);">%s</span>' % text
elif style == 'success':
text = '<span style="font-weight: bold; color: rgb(8, 68, 0);">%s</span>' % text
else: else:
text = '<span>%s</span>' % text # without span <br/> is ignored!!! text = '<span>%s</span>' % text # without span <br/> is ignored!!!
@ -304,6 +308,16 @@ class TermWidget(QWidget):
""" """
self._append_to_browser('out', text) self._append_to_browser('out', text)
def append_success(self, text):
"""Appent text to output widget
"""
self._append_to_browser('success', text)
def append_warning(self, text):
"""Appent text to output widget
"""
self._append_to_browser('warning', text)
def append_error(self, text): def append_error(self, text):
"""Appent error text to output widget. Text is drawn with red background """Appent error text to output widget. Text is drawn with red background
""" """

View File

@ -355,7 +355,17 @@ class ToolTransform(FlatCAMTool):
self.offx_entry.returnPressed.connect(self.on_offx) self.offx_entry.returnPressed.connect(self.on_offx)
self.offy_entry.returnPressed.connect(self.on_offy) self.offy_entry.returnPressed.connect(self.on_offy)
def run(self):
self.app.report_usage("ToolTransform()")
FlatCAMTool.run(self)
self.set_tool_ui()
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)
def set_tool_ui(self):
## Initialize form ## Initialize form
self.rotate_entry.set_value('0') self.rotate_entry.set_value('0')
self.skewx_entry.set_value('0') self.skewx_entry.set_value('0')
@ -366,18 +376,16 @@ class ToolTransform(FlatCAMTool):
self.offy_entry.set_value('0') self.offy_entry.set_value('0')
self.flip_ref_cb.setChecked(False) self.flip_ref_cb.setChecked(False)
def run(self):
FlatCAMTool.run(self)
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)
def on_rotate(self): def on_rotate(self):
try: try:
value = float(self.rotate_entry.get_value()) value = float(self.rotate_entry.get_value())
except Exception as e: except ValueError:
self.app.inform.emit("[error] Failed to rotate due of: %s" % str(e)) # try to convert comma to decimal point. if it's still not working error message and return
try:
value = float(self.rotate_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Rotate, "
"use a number.")
return return
self.app.worker_task.emit({'fcn': self.on_rotate_action, self.app.worker_task.emit({'fcn': self.on_rotate_action,
'params': [value]}) 'params': [value]})
@ -405,9 +413,15 @@ class ToolTransform(FlatCAMTool):
def on_skewx(self): def on_skewx(self):
try: try:
value = float(self.skewx_entry.get_value()) value = float(self.skewx_entry.get_value())
except: except ValueError:
self.app.inform.emit("[warning_notcl] No value for Skew!") # try to convert comma to decimal point. if it's still not working error message and return
try:
value = float(self.skewx_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Skew X, "
"use a number.")
return return
# self.on_skew("X", value) # self.on_skew("X", value)
axis = 'X' axis = 'X'
self.app.worker_task.emit({'fcn': self.on_skew, self.app.worker_task.emit({'fcn': self.on_skew,
@ -417,9 +431,15 @@ class ToolTransform(FlatCAMTool):
def on_skewy(self): def on_skewy(self):
try: try:
value = float(self.skewy_entry.get_value()) value = float(self.skewy_entry.get_value())
except: except ValueError:
self.app.inform.emit("[warning_notcl] No value for Skew!") # try to convert comma to decimal point. if it's still not working error message and return
try:
value = float(self.skewy_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Skew Y, "
"use a number.")
return return
# self.on_skew("Y", value) # self.on_skew("Y", value)
axis = 'Y' axis = 'Y'
self.app.worker_task.emit({'fcn': self.on_skew, self.app.worker_task.emit({'fcn': self.on_skew,
@ -429,9 +449,15 @@ class ToolTransform(FlatCAMTool):
def on_scalex(self): def on_scalex(self):
try: try:
xvalue = float(self.scalex_entry.get_value()) xvalue = float(self.scalex_entry.get_value())
except: except ValueError:
self.app.inform.emit("[warning_notcl] No value for Scale!") # try to convert comma to decimal point. if it's still not working error message and return
try:
xvalue = float(self.scalex_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Scale X, "
"use a number.")
return return
# scaling to zero has no sense so we remove it, because scaling with 1 does nothing # scaling to zero has no sense so we remove it, because scaling with 1 does nothing
if xvalue == 0: if xvalue == 0:
xvalue = 1 xvalue = 1
@ -457,9 +483,15 @@ class ToolTransform(FlatCAMTool):
xvalue = 1 xvalue = 1
try: try:
yvalue = float(self.scaley_entry.get_value()) yvalue = float(self.scaley_entry.get_value())
except: except ValueError:
self.app.inform.emit("[warning_notcl] No value for Scale!") # try to convert comma to decimal point. if it's still not working error message and return
try:
yvalue = float(self.scaley_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Scale Y, "
"use a number.")
return return
# scaling to zero has no sense so we remove it, because scaling with 1 does nothing # scaling to zero has no sense so we remove it, because scaling with 1 does nothing
if yvalue == 0: if yvalue == 0:
yvalue = 1 yvalue = 1
@ -480,9 +512,15 @@ class ToolTransform(FlatCAMTool):
def on_offx(self): def on_offx(self):
try: try:
value = float(self.offx_entry.get_value()) value = float(self.offx_entry.get_value())
except: except ValueError:
self.app.inform.emit("[warning_notcl] No value for Offset!") # try to convert comma to decimal point. if it's still not working error message and return
try:
value = float(self.offx_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Offset X, "
"use a number.")
return return
# self.on_offset("X", value) # self.on_offset("X", value)
axis = 'X' axis = 'X'
self.app.worker_task.emit({'fcn': self.on_offset, self.app.worker_task.emit({'fcn': self.on_offset,
@ -492,9 +530,15 @@ class ToolTransform(FlatCAMTool):
def on_offy(self): def on_offy(self):
try: try:
value = float(self.offy_entry.get_value()) value = float(self.offy_entry.get_value())
except: except ValueError:
self.app.inform.emit("[warning_notcl] No value for Offset!") # try to convert comma to decimal point. if it's still not working error message and return
try:
value = float(self.offy_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered for Offset Y, "
"use a number.")
return return
# self.on_offset("Y", value) # self.on_offset("Y", value)
axis = 'Y' axis = 'Y'
self.app.worker_task.emit({'fcn': self.on_offset, self.app.worker_task.emit({'fcn': self.on_offset,
@ -509,7 +553,7 @@ class ToolTransform(FlatCAMTool):
ymaxlist = [] ymaxlist = []
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to rotate!") self.app.inform.emit("[WARNING_NOTCL] No object selected. Please Select an object to rotate!")
return return
else: else:
with self.app.proc_container.new("Appying Rotate"): with self.app.proc_container.new("Appying Rotate"):
@ -550,7 +594,7 @@ class ToolTransform(FlatCAMTool):
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
self.app.inform.emit("[error_notcl] Due of %s, rotation movement was not executed." % str(e)) self.app.inform.emit("[ERROR_NOTCL] Due of %s, rotation movement was not executed." % str(e))
return return
def on_flip(self, axis): def on_flip(self, axis):
@ -561,7 +605,7 @@ class ToolTransform(FlatCAMTool):
ymaxlist = [] ymaxlist = []
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to flip!") self.app.inform.emit("[WARNING_NOTCL] No object selected. Please Select an object to flip!")
return return
else: else:
with self.app.proc_container.new("Applying Flip"): with self.app.proc_container.new("Applying Flip"):
@ -623,7 +667,7 @@ class ToolTransform(FlatCAMTool):
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
self.app.inform.emit("[error_notcl] Due of %s, Flip action was not executed." % str(e)) self.app.inform.emit("[ERROR_NOTCL] Due of %s, Flip action was not executed." % str(e))
return return
def on_skew(self, axis, num): def on_skew(self, axis, num):
@ -632,7 +676,7 @@ class ToolTransform(FlatCAMTool):
yminlist = [] yminlist = []
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to shear/skew!") self.app.inform.emit("[WARNING_NOTCL] No object selected. Please Select an object to shear/skew!")
return return
else: else:
with self.app.proc_container.new("Applying Skew"): with self.app.proc_container.new("Applying Skew"):
@ -670,7 +714,7 @@ class ToolTransform(FlatCAMTool):
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
self.app.inform.emit("[error_notcl] Due of %s, Skew action was not executed." % str(e)) self.app.inform.emit("[ERROR_NOTCL] Due of %s, Skew action was not executed." % str(e))
return return
def on_scale(self, axis, xfactor, yfactor, point=None): def on_scale(self, axis, xfactor, yfactor, point=None):
@ -681,7 +725,7 @@ class ToolTransform(FlatCAMTool):
ymaxlist = [] ymaxlist = []
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to scale!") self.app.inform.emit("[WARNING_NOTCL] No object selected. Please Select an object to scale!")
return return
else: else:
with self.app.proc_container.new("Applying Scale"): with self.app.proc_container.new("Applying Scale"):
@ -725,7 +769,7 @@ class ToolTransform(FlatCAMTool):
self.app.inform.emit('Object(s) were scaled on %s axis ...' % str(axis)) self.app.inform.emit('Object(s) were scaled on %s axis ...' % str(axis))
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
self.app.inform.emit("[error_notcl] Due of %s, Scale action was not executed." % str(e)) self.app.inform.emit("[ERROR_NOTCL] Due of %s, Scale action was not executed." % str(e))
return return
def on_offset(self, axis, num): def on_offset(self, axis, num):
@ -734,7 +778,7 @@ class ToolTransform(FlatCAMTool):
yminlist = [] yminlist = []
if not obj_list: if not obj_list:
self.app.inform.emit("[warning_notcl] No object selected. Please Select an object to offset!") self.app.inform.emit("[WARNING_NOTCL] No object selected. Please Select an object to offset!")
return return
else: else:
with self.app.proc_container.new("Applying Offset"): with self.app.proc_container.new("Applying Offset"):
@ -771,7 +815,7 @@ class ToolTransform(FlatCAMTool):
self.app.progress.emit(100) self.app.progress.emit(100)
except Exception as e: except Exception as e:
self.app.inform.emit("[error_notcl] Due of %s, Offset action was not executed." % str(e)) self.app.inform.emit("[ERROR_NOTCL] Due of %s, Offset action was not executed." % str(e))
return return
# end of file # end of file

View File

@ -5,7 +5,8 @@ from flatcamTools.ToolPanelize import Panelize
from flatcamTools.ToolFilm import Film from flatcamTools.ToolFilm import Film
from flatcamTools.ToolMove import ToolMove from flatcamTools.ToolMove import ToolMove
from flatcamTools.ToolDblSided import DblSidedTool from flatcamTools.ToolDblSided import DblSidedTool
from flatcamTools.ToolCutout import ToolCutout
from flatcamTools.ToolCutOut import ToolCutOut
from flatcamTools.ToolCalculators import ToolCalculator from flatcamTools.ToolCalculators import ToolCalculator
from flatcamTools.ToolProperties import Properties from flatcamTools.ToolProperties import Properties
from flatcamTools.ToolImage import ToolImage from flatcamTools.ToolImage import ToolImage

View File

@ -0,0 +1,261 @@
from FlatCAMPostProc import *
class Toolchange_Probe_MACH3(FlatCAMPostProc):
coordinate_format = "%.*f"
feedrate_format = '%.*f'
def start_code(self, p):
units = ' ' + str(p['units']).lower()
coords_xy = p['toolchange_xy']
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'])
if str(p['options']['type']) == 'Geometry':
gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
if str(p['options']['type']) == 'Geometry':
gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + units + '/min' + ')\n'
gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
gcode += '(Feedrate Probe ' + str(p['feedrate_probe']) + units + '/min' + ')\n' + '\n'
gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
if str(p['options']['type']) == 'Geometry':
if p['multidepth'] is True:
gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
if coords_xy is not None:
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
else:
gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
gcode += '(Z Probe Depth: ' + str(p['z_pdepth']) + units + ')\n'
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
gcode += 'G90\n'
gcode += 'G17\n'
gcode += 'G94\n'
return gcode
def startz_code(self, p):
return ''
def lift_code(self, p):
return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
def down_code(self, p):
return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
def toolchange_code(self, p):
toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1]
no_drills = 1
if int(p.tool) == 1 and p.startz is not None:
toolchangez = p.startz
if p.units.upper() == 'MM':
toolC_formatted = format(p.toolC, '.2f')
else:
toolC_formatted = format(p.toolC, '.4f')
if str(p['options']['type']) == 'Excellon':
for i in p['options']['Tools_in_use']:
if i[0] == p.tool:
no_drills = i[2]
if toolchangexy is not None:
gcode = """
T{tool}
M5
M6
G00 Z{toolchangez}
G00 X{toolchangex} Y{toolchangey}
(MSG, Change to Tool Dia = {toolC} ||| Drills for this tool = {t_drills} ||| Tool Probing MACH3)
M0
G00 Z{z_move}
F{feedrate_probe}
G31 Z{z_pdepth}
G92 Z0
G00 Z{z_move}
F{feedrate_probe_slow}
G31 Z{z_pdepth}
G92 Z0
(MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
M0
G00 Z{z_move}
""".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),
z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
else:
gcode = """
T{tool}
M5
M6
G00 Z{toolchangez}
(MSG, Change to Tool Dia = {toolC} ||| Drills for this tool = {t_drills} ||| Tool Probing MACH3)
M0
G00 Z{z_move}
F{feedrate_probe}
G31 Z{z_pdepth}
G92 Z0
G00 Z{z_move}
F{feedrate_probe_slow}
G31 Z{z_pdepth}
G92 Z0
(MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
M0
G00 Z{z_move}
""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
# if f_plunge is True:
# gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
else:
if toolchangexy is not None:
gcode = """
T{tool}
M5
M6
G00 Z{toolchangez}
G00 X{toolchangex} Y{toolchangey}
(MSG, Change to Tool Dia = {toolC} ||| Tool Probing MACH3)
M0
G00 Z{z_move}
F{feedrate_probe}
G31 Z{z_pdepth}
G92 Z0
G00 Z{z_move}
F{feedrate_probe_slow}
G31 Z{z_pdepth}
G92 Z0
(MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
M0
G00 Z{z_move}
""".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),
z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
tool=int(p.tool),
toolC=toolC_formatted)
else:
gcode = """
T{tool}
M5
M6
G00 Z{toolchangez}
(MSG, Change to Tool Dia = {toolC} ||| Tool Probing MACH3)
M0
G00 Z{z_move}
F{feedrate_probe}
G31 Z{z_pdepth}
G92 Z0
G00 Z{z_move}
F{feedrate_probe_slow}
G31 Z{z_pdepth}
G92 Z0
(MSG, Remove any clips or other devices used for probing. CNC work is resuming ...)
M0
G00 Z{z_move}
""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
z_move=self.coordinate_format % (p.coords_decimals, p.z_move),
feedrate_probe=str(self.feedrate_format %(p.fr_decimals, p.feedrate_probe)),
feedrate_probe_slow=str(self.feedrate_format % (p.fr_decimals, (p.feedrate_probe / 2))),
z_pdepth=self.coordinate_format % (p.coords_decimals, p.z_pdepth),
tool=int(p.tool),
toolC=toolC_formatted)
# if f_plunge is True:
# gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
def up_to_zero_code(self, p):
return 'G01 Z0'
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)
def linear_code(self, p):
return ('G01 ' + self.position_code(p)).format(**p)
def end_code(self, p):
coords_xy = p['toolchange_xy']
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\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_code(self, p):
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
def feedrate_z_code(self, p):
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
def spindle_code(self, p):
if p.spindlespeed:
return 'M03 S' + str(p.spindlespeed)
else:
return 'M03'
def dwell_code(self, p):
if p.dwelltime:
return 'G4 P' + str(p.dwelltime)
def spindle_stop_code(self,p):
return 'M05'

View File

@ -0,0 +1,191 @@
from FlatCAMPostProc import *
class Toolchange_Probe_general(FlatCAMPostProc):
coordinate_format = "%.*f"
feedrate_format = '%.*f'
def start_code(self, p):
units = ' ' + str(p['units']).lower()
coords_xy = p['toolchange_xy']
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'])
if str(p['options']['type']) == 'Geometry':
gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
if str(p['options']['type']) == 'Geometry':
gcode += '(Feedrate_Z: ' + str(p['feedrate_z']) + units + '/min' + ')\n'
gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
gcode += '(Z_Cut: ' + str(p['z_cut']) + units + ')\n'
if str(p['options']['type']) == 'Geometry':
if p['multidepth'] is True:
gcode += '(DepthPerCut: ' + str(p['depthpercut']) + units + ' <=>' + \
str(math.ceil(abs(p['z_cut']) / p['depthpercut'])) + ' passes' + ')\n'
gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
if coords_xy is not None:
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
else:
gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
gcode += 'G90\n'
gcode += 'G17\n'
gcode += 'G94\n'
return gcode
def startz_code(self, p):
return ''
def lift_code(self, p):
return 'G00 Z' + self.coordinate_format%(p.coords_decimals, p.z_move)
def down_code(self, p):
return 'G01 Z' + self.coordinate_format%(p.coords_decimals, p.z_cut)
def toolchange_code(self, p):
toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1]
no_drills = 1
if int(p.tool) == 1 and p.startz is not None:
toolchangez = p.startz
if p.units.upper() == 'MM':
toolC_formatted = format(p.toolC, '.2f')
else:
toolC_formatted = format(p.toolC, '.4f')
if str(p['options']['type']) == 'Excellon':
for i in p['options']['Tools_in_use']:
if i[0] == p.tool:
no_drills = i[2]
if toolchangexy is not None:
gcode = """
G00 X{toolchangex} Y{toolchangey}
T{tool}
M5
M6
(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0
""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
else:
gcode = """
T{tool}
M5
M6
(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0
""".format(tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
else:
if toolchangexy is not None:
gcode = """
G00 X{toolchangex} Y{toolchangey}
T{tool}
M5
M6
(MSG, Change to Tool Dia = {toolC})
M0
""".format(toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
tool=int(p.tool),
toolC=toolC_formatted)
else:
gcode = """
T{tool}
M5
M6
(MSG, Change to Tool Dia = {toolC})
M0""".format(tool=int(p.tool),
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
def up_to_zero_code(self, p):
return 'G01 Z0'
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)
def linear_code(self, p):
return ('G01 ' + self.position_code(p)).format(**p)
def end_code(self, p):
coords_xy = p['toolchange_xy']
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\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_code(self, p):
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
def feedrate_z_code(self, p):
return 'G01 F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate_z))
def spindle_code(self, p):
if p.spindlespeed:
return 'M03 S' + str(p.spindlespeed)
else:
return 'M03'
def dwell_code(self, p):
if p.dwelltime:
return 'G4 P' + str(p.dwelltime)
def spindle_stop_code(self,p):
return 'M05'

View File

@ -1,7 +1,7 @@
from FlatCAMPostProc import * from FlatCAMPostProc import *
class manual_toolchange(FlatCAMPostProc): class Toolchange_manual(FlatCAMPostProc):
coordinate_format = "%.*f" coordinate_format = "%.*f"
feedrate_format = '%.*f' feedrate_format = '%.*f'
@ -11,6 +11,11 @@ class manual_toolchange(FlatCAMPostProc):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = '' 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'])
if str(p['options']['type']) == 'Geometry': if str(p['options']['type']) == 'Geometry':
gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n' gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
@ -29,20 +34,27 @@ class manual_toolchange(FlatCAMPostProc):
gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n' gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n' gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
if coords_xy is not None:
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n' gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
else:
gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
gcode += '(Z Start: ' + str(p['startz']) + units + ')\n' gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
gcode += '(Z End: ' + str(p['endz']) + units + ')\n' gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
else: else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed']) gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n') gcode += ('G20\n' if p.units.upper() == 'IN' else 'G21\n')
gcode += 'G90\n' gcode += 'G90\n'
gcode += 'G17\n'
gcode += 'G94\n' gcode += 'G94\n'
return gcode return gcode
@ -62,8 +74,15 @@ class manual_toolchange(FlatCAMPostProc):
def toolchange_code(self, p): def toolchange_code(self, p):
toolchangez = p.toolchangez toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0] toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1] toolchangey = toolchangexy[1]
# else:
# toolchangex = p.oldx
# toolchangey = p.oldy
no_drills = 1 no_drills = 1
@ -79,11 +98,13 @@ class manual_toolchange(FlatCAMPostProc):
for i in p['options']['Tools_in_use']: for i in p['options']['Tools_in_use']:
if i[0] == p.tool: if i[0] == p.tool:
no_drills = i[2] no_drills = i[2]
return """G00 Z{toolchangez}
if toolchangexy is not None:
gcode = """G00 Z{toolchangez}
T{tool} T{tool}
M5 M5
G00 X{toolchangex} Y{toolchangey} G00 X{toolchangex} Y{toolchangey}
(MSG, Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills}) (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0 M0
G01 Z0 G01 Z0
M0 M0
@ -95,8 +116,30 @@ M0
tool=int(p.tool), tool=int(p.tool),
t_drills=no_drills, t_drills=no_drills,
toolC=toolC_formatted) toolC=toolC_formatted)
else: else:
return """G00 Z{toolchangez} gcode = """G00 Z{toolchangez}
T{tool}
M5
(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0
G01 Z0
M0
G00 Z{toolchangez}
M0
""".format(
toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
else:
if toolchangexy is not None:
gcode = """G00 Z{toolchangez}
T{tool} T{tool}
M5 M5
G00 X{toolchangex}Y{toolchangey} G00 X{toolchangex}Y{toolchangey}
@ -111,6 +154,23 @@ M0
toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez), toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
tool=int(p.tool), tool=int(p.tool),
toolC=toolC_formatted) toolC=toolC_formatted)
else:
gcode = """G00 Z{toolchangez}
T{tool}
M5
(MSG, Change to Tool Dia = {toolC})
M0
G01 Z0
M0
G00 Z{toolchangez}
M0
""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
tool=int(p.tool),
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
def up_to_zero_code(self, p): def up_to_zero_code(self, p):
return 'G01 Z0' return 'G01 Z0'
@ -128,7 +188,10 @@ M0
def end_code(self, p): def end_code(self, p):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n") gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
if coords_xy is not None:
gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n" gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
else:
gcode += 'G00 X0 Y0' + "\n"
return gcode return gcode
def feedrate_code(self, p): def feedrate_code(self, p):

View File

@ -11,6 +11,11 @@ class default(FlatCAMPostProc):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = '' 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'])
if str(p['options']['type']) == 'Geometry': if str(p['options']['type']) == 'Geometry':
gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n' gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
@ -29,7 +34,12 @@ class default(FlatCAMPostProc):
gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n' gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n' gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
if coords_xy is not None:
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n' gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
else:
gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
gcode += '(Z Start: ' + str(p['startz']) + units + ')\n' gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
gcode += '(Z End: ' + str(p['endz']) + units + ')\n' gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@ -37,7 +47,10 @@ class default(FlatCAMPostProc):
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
else: else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed']) gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
@ -62,6 +75,10 @@ class default(FlatCAMPostProc):
def toolchange_code(self, p): def toolchange_code(self, p):
toolchangez = p.toolchangez toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0] toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1] toolchangey = toolchangexy[1]
@ -79,17 +96,49 @@ class default(FlatCAMPostProc):
for i in p['options']['Tools_in_use']: for i in p['options']['Tools_in_use']:
if i[0] == p.tool: if i[0] == p.tool:
no_drills = i[2] no_drills = i[2]
return """G00 Z{toolchangez}
if toolchangexy is not None:
gcode = """G00 Z{toolchangez}
G00 X{toolchangex} Y{toolchangey}
T{tool} T{tool}
M5 M5
M6 M6
(MSG, Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills}) (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez), 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(p.tool), tool=int(p.tool),
t_drills=no_drills, t_drills=no_drills,
toolC=toolC_formatted) toolC=toolC_formatted)
else: else:
return """G00 Z{toolchangez} gcode = """G00 Z{toolchangez}
T{tool}
M5
M6
(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
else:
if toolchangexy is not None:
gcode = """G00 Z{toolchangez}
G00 X{toolchangex} Y{toolchangey}
T{tool}
M5
M6
(MSG, Change to Tool 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(p.tool),
toolC=toolC_formatted)
else:
gcode = """G00 Z{toolchangez}
T{tool} T{tool}
M5 M5
M6 M6
@ -98,6 +147,10 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
tool=int(p.tool), tool=int(p.tool),
toolC=toolC_formatted) toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
def up_to_zero_code(self, p): def up_to_zero_code(self, p):
return 'G01 Z0' return 'G01 Z0'
@ -114,6 +167,8 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
def end_code(self, p): def end_code(self, p):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n") gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
if coords_xy is not None:
gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n" gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
return gcode return gcode

View File

@ -11,6 +11,11 @@ class grbl_11(FlatCAMPostProc):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = '' 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'])
if str(p['options']['type']) == 'Geometry': if str(p['options']['type']) == 'Geometry':
gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n' + '\n' gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n' + '\n'
@ -29,7 +34,10 @@ class grbl_11(FlatCAMPostProc):
gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n' gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n' gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
if coords_xy is not None:
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n' gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
else:
gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
gcode += '(Z Start: ' + str(p['startz']) + units + ')\n' gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
gcode += '(Z End: ' + str(p['endz']) + units + ')\n' gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@ -37,7 +45,10 @@ class grbl_11(FlatCAMPostProc):
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
else: else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
gcode += '(Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + ')\n' + '\n' gcode += '(Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + ')\n' + '\n'
@ -62,6 +73,15 @@ class grbl_11(FlatCAMPostProc):
def toolchange_code(self, p): def toolchange_code(self, p):
toolchangez = p.toolchangez toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1]
no_drills = 1
if int(p.tool) == 1 and p.startz is not None: if int(p.tool) == 1 and p.startz is not None:
toolchangez = p.startz toolchangez = p.startz
@ -75,17 +95,50 @@ class grbl_11(FlatCAMPostProc):
for i in p['options']['Tools_in_use']: for i in p['options']['Tools_in_use']:
if i[0] == p.tool: if i[0] == p.tool:
no_drills = i[2] no_drills = i[2]
return """G00 Z{toolchangez}
if toolchangexy is not None:
gcode = """G00 Z{toolchangez}
G00 X{toolchangex} Y{toolchangey}
T{tool} T{tool}
M5 M5
M6 M6
(MSG, Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills}) (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez), 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(p.tool), tool=int(p.tool),
t_drills=no_drills, t_drills=no_drills,
toolC=toolC_formatted) toolC=toolC_formatted)
else: else:
return """G00 Z{toolchangez} gcode = """G00 Z{toolchangez}
T{tool}
M5
M6
(MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
else:
if toolchangexy is not None:
gcode = """G00 Z{toolchangez}
G00 X{toolchangex} Y{toolchangey}
T{tool}
M5
M6
(MSG, Change to Tool 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(p.tool),
toolC=toolC_formatted)
else:
gcode = """G00 Z{toolchangez}
T{tool} T{tool}
M5 M5
M6 M6
@ -94,6 +147,10 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
tool=int(p.tool), tool=int(p.tool),
toolC=toolC_formatted) toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG00 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
def up_to_zero_code(self, p): def up_to_zero_code(self, p):
return 'G01 Z0' return 'G01 Z0'
@ -110,7 +167,9 @@ M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez)
def end_code(self, p): def end_code(self, p):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = ('G00 Z' + self.feedrate_format % (p.fr_decimals, p.endz) + "\n") gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
if coords_xy is not None:
gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n" gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
return gcode return gcode

View File

@ -13,6 +13,11 @@ class grbl_laser(FlatCAMPostProc):
units = ' ' + str(p['units']).lower() units = ' ' + str(p['units']).lower()
gcode = '' 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 += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n' gcode += '(Feedrate: ' + str(p['feedrate']) + units + '/min' + ')\n'
gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n' gcode += '(Feedrate rapids ' + str(p['feedrate_rapid']) + units + '/min' + ')\n' + '\n'
@ -22,7 +27,11 @@ class grbl_laser(FlatCAMPostProc):
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
else: else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n'
gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\n" gcode += ('G20' if p.units.upper() == 'IN' else 'G21') + "\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 += 'G90\n' gcode += 'G90\n'
gcode += 'G94\n' gcode += 'G94\n'
gcode += 'G17\n' gcode += 'G17\n'
@ -59,8 +68,11 @@ class grbl_laser(FlatCAMPostProc):
' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate)) ' F' + str(self.feedrate_format %(p.fr_decimals, p.feedrate))
def end_code(self, p): def end_code(self, p):
coords_xy = p['toolchange_xy']
gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n") gcode = ('G00 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + "\n")
gcode += 'G00 X0Y0'
if coords_xy is not None:
gcode += 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
return gcode return gcode
def feedrate_code(self, p): def feedrate_code(self, p):

View File

@ -11,6 +11,11 @@ class line_xyz(FlatCAMPostProc):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = '' 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'])
if str(p['options']['type']) == 'Geometry': if str(p['options']['type']) == 'Geometry':
gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n' gcode += '(TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + ')\n'
@ -29,7 +34,10 @@ class line_xyz(FlatCAMPostProc):
gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n' gcode += '(Z_Move: ' + str(p['z_move']) + units + ')\n'
gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n' gcode += '(Z Toolchange: ' + str(p['toolchangez']) + units + ')\n'
if coords_xy is not None:
gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n' gcode += '(X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + ')\n'
else:
gcode += '(X,Y Toolchange: ' + "None" + units + ')\n'
gcode += '(Z Start: ' + str(p['startz']) + units + ')\n' gcode += '(Z Start: ' + str(p['startz']) + units + ')\n'
gcode += '(Z End: ' + str(p['endz']) + units + ')\n' gcode += '(Z End: ' + str(p['endz']) + units + ')\n'
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
@ -37,7 +45,10 @@ class line_xyz(FlatCAMPostProc):
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n'
else: else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'
gcode += '(X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + ')\n'
gcode += '(Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + ')\n\n'
gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed']) gcode += '(Spindle Speed: %s RPM)\n' % str(p['spindlespeed'])
@ -71,8 +82,19 @@ class line_xyz(FlatCAMPostProc):
def toolchange_code(self, p): def toolchange_code(self, p):
toolchangez = p.toolchangez toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0] toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1] toolchangey = toolchangexy[1]
else:
if str(p['options']['type']) == 'Excellon':
toolchangex = p.oldx
toolchangey = p.oldy
else:
toolchangex = p.x
toolchangey = p.y
no_drills = 1 no_drills = 1
@ -88,19 +110,26 @@ class line_xyz(FlatCAMPostProc):
for i in p['options']['Tools_in_use']: for i in p['options']['Tools_in_use']:
if i[0] == p.tool: if i[0] == p.tool:
no_drills = i[2] no_drills = i[2]
return """G00 X{toolchangex} Y{toolchangey} Z{toolchangez} gcode = """G00 X{toolchangex} Y{toolchangey} Z{toolchangez}
T{tool} T{tool}
M5 M5
M6 M6
(MSG, Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills}) (MSG, Change to Tool Dia = {toolC} ||| Total drills for tool T{tool} = {t_drills})
M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex), M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey), toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez), toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
tool=int(p.tool), tool=int(p.tool),
t_drills=no_drills, t_drills=no_drills,
toolC=toolC_formatted) toolC=toolC_formatted)
if f_plunge is True:
gcode += """\nG00 X{toolchangex} Y{toolchangey} Z{z_move}""".format(
toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex),
toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
z_move=self.coordinate_format % (p.coords_decimals, p.z_move))
return gcode
else: else:
return """G00 X{toolchangex} Y{toolchangey} Z{toolchangez} gcode = """G00 X{toolchangex} Y{toolchangey} Z{toolchangez}
T{tool} T{tool}
M5 M5
M6 M6
@ -111,6 +140,13 @@ M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex)
tool=int(p.tool), tool=int(p.tool),
toolC=toolC_formatted) toolC=toolC_formatted)
if f_plunge is True:
gcode += """\nG00 X{toolchangex} Y{toolchangey} Z{z_move}""".format(
toolchangex=self.coordinate_format % (p.coords_decimals, toolchangex),
toolchangey=self.coordinate_format % (p.coords_decimals, toolchangey),
z_move=self.coordinate_format % (p.coords_decimals, p.z_move))
return gcode
def up_to_zero_code(self, p): def up_to_zero_code(self, p):
g = 'G01 ' + 'X' + self.coordinate_format % (p.coords_decimals, p.x) + \ g = 'G01 ' + 'X' + self.coordinate_format % (p.coords_decimals, p.x) + \
' Y' + self.coordinate_format % (p.coords_decimals, p.y) + \ ' Y' + self.coordinate_format % (p.coords_decimals, p.y) + \
@ -132,6 +168,10 @@ M0""".format(toolchangex=self.coordinate_format%(p.coords_decimals, toolchangex)
return g return g
def end_code(self, p): def end_code(self, p):
coords_xy = p['toolchange_xy']
if coords_xy is not None:
g = 'G00 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + "\n"
else:
g = ('G00 ' + self.position_code(p)).format(**p) g = ('G00 ' + self.position_code(p)).format(**p)
g += ' Z' + self.coordinate_format % (p.coords_decimals, p.endz) g += ' Z' + self.coordinate_format % (p.coords_decimals, p.endz)
return g return g

View File

@ -9,8 +9,14 @@ class marlin(FlatCAMPostProc):
def start_code(self, p): def start_code(self, p):
units = ' ' + str(p['units']).lower() units = ' ' + str(p['units']).lower()
coords_xy = p['toolchange_xy']
gcode = '' 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'])
if str(p['options']['type']) == 'Geometry': if str(p['options']['type']) == 'Geometry':
gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n' + '\n' gcode += ';TOOL DIAMETER: ' + str(p['options']['tool_dia']) + units + '\n' + '\n'
@ -29,6 +35,12 @@ class marlin(FlatCAMPostProc):
gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n' gcode += ';Z_Move: ' + str(p['z_move']) + units + '\n'
gcode += ';Z Toolchange: ' + str(p['toolchangez']) + units + '\n' gcode += ';Z Toolchange: ' + str(p['toolchangez']) + units + '\n'
if coords_xy is not None:
gcode += ';X,Y Toolchange: ' + "%.4f, %.4f" % (coords_xy[0], coords_xy[1]) + units + '\n'
else:
gcode += ';X,Y Toolchange: ' + "None" + units + '\n'
gcode += ';Z Start: ' + str(p['startz']) + units + '\n' gcode += ';Z Start: ' + str(p['startz']) + units + '\n'
gcode += ';Z End: ' + str(p['endz']) + units + '\n' gcode += ';Z End: ' + str(p['endz']) + units + '\n'
gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n' gcode += ';Steps per circle: ' + str(p['steps_per_circle']) + '\n'
@ -36,7 +48,10 @@ class marlin(FlatCAMPostProc):
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += ';Postprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n' gcode += ';Postprocessor Excellon: ' + str(p['pp_excellon_name']) + '\n'
else: else:
gcode += ';Postprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' gcode += ';Postprocessor Geometry: ' + str(p['pp_geometry_name']) + '\n' + '\n'
gcode += ';X range: ' + '{: >9s}'.format(xmin) + ' ... ' + '{: >9s}'.format(xmax) + ' ' + units + '\n'
gcode += ';Y range: ' + '{: >9s}'.format(ymin) + ' ... ' + '{: >9s}'.format(ymax) + ' ' + units + '\n\n'
gcode += ';Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + '\n' + '\n' gcode += ';Spindle Speed: ' + str(p['spindlespeed']) + ' RPM' + '\n' + '\n'
@ -59,6 +74,14 @@ class marlin(FlatCAMPostProc):
def toolchange_code(self, p): def toolchange_code(self, p):
toolchangez = p.toolchangez toolchangez = p.toolchangez
toolchangexy = p.toolchange_xy
f_plunge = p.f_plunge
gcode = ''
if toolchangexy is not None:
toolchangex = toolchangexy[0]
toolchangey = toolchangexy[1]
no_drills = 1 no_drills = 1
if int(p.tool) == 1 and p.startz is not None: if int(p.tool) == 1 and p.startz is not None:
@ -73,20 +96,61 @@ class marlin(FlatCAMPostProc):
for i in p['options']['Tools_in_use']: for i in p['options']['Tools_in_use']:
if i[0] == p.tool: if i[0] == p.tool:
no_drills = i[2] no_drills = i[2]
return """G0 Z{toolchangez}
if toolchangexy is not None:
gcode = """G0 Z{toolchangez}
G0 X{toolchangex} Y{toolchangey}
T{tool}
M5 M5
M0 Change to Tool Dia = {toolC}, Total drills for current tool = {t_drills} M6
""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez), ;MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
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(p.tool), tool=int(p.tool),
t_drills=no_drills, t_drills=no_drills,
toolC=toolC_formatted) toolC=toolC_formatted)
else: else:
return """G0 Z{toolchangez} gcode = """G0 Z{toolchangez}
T{tool}
M5 M5
M0 Change to Tool Dia = {toolC} M6
""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez), ;MSG, Change to Tool Dia = {toolC}, Total drills for tool T{tool} = {t_drills}
M0""".format(toolchangez=self.coordinate_format % (p.coords_decimals, toolchangez),
tool=int(p.tool),
t_drills=no_drills,
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG0 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
else:
if toolchangexy is not None:
gcode = """G0 Z{toolchangez}
G0 X{toolchangex} Y{toolchangey}
T{tool}
M5
M6
;MSG, Change to Tool 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(p.tool), tool=int(p.tool),
toolC=toolC_formatted) toolC=toolC_formatted)
else:
gcode = """G0 Z{toolchangez}
T{tool}
M5
M6
;MSG, Change to Tool Dia = {toolC}
M0""".format(toolchangez=self.coordinate_format%(p.coords_decimals, toolchangez),
tool=int(p.tool),
toolC=toolC_formatted)
if f_plunge is True:
gcode += '\nG0 Z%.*f' % (p.coords_decimals, p.z_move)
return gcode
def up_to_zero_code(self, p): def up_to_zero_code(self, p):
return 'G1 Z0' + " " + self.feedrate_code(p) return 'G1 Z0' + " " + self.feedrate_code(p)
@ -104,6 +168,8 @@ M0 Change to Tool Dia = {toolC}
def end_code(self, p): def end_code(self, p):
coords_xy = p['toolchange_xy'] coords_xy = p['toolchange_xy']
gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n") gcode = ('G0 Z' + self.feedrate_format %(p.fr_decimals, p.endz) + " " + self.feedrate_rapid_code(p) + "\n")
if coords_xy is not None:
gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n" gcode += 'G0 X{x} Y{y}'.format(x=coords_xy[0], y=coords_xy[1]) + " " + self.feedrate_rapid_code(p) + "\n"
return gcode return gcode

BIN
share/fscreen32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
share/plot32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

View File

@ -55,6 +55,9 @@ class TclCommandCncjob(TclCommandSignaled):
('multidepth', 'Use or not multidepth cnccut. (True or False)'), ('multidepth', 'Use or not multidepth cnccut. (True or False)'),
('depthperpass', 'Height of one layer for multidepth.'), ('depthperpass', 'Height of one layer for multidepth.'),
('extracut', 'Use or not an extra cnccut over the first point in path,in the job end (example: True)'), ('extracut', 'Use or not an extra cnccut over the first point in path,in the job end (example: True)'),
('toolchange', 'Enable tool changes (example: True).'),
('toolchangez', 'Z distance for toolchange (example: 30.0).'),
('toolchangexy', 'X, Y coordonates for toolchange in format (x, y) (example: (2.0, 3.1) ).'),
('endz', 'Height where the last move will park.'), ('endz', 'Height where the last move will park.'),
('outname', 'Name of the resulting Geometry object.'), ('outname', 'Name of the resulting Geometry object.'),
('ppname_g', 'Name of the Geometry postprocessor. No quotes, case sensitive') ('ppname_g', 'Name of the Geometry postprocessor. No quotes, case sensitive')
@ -96,6 +99,10 @@ class TclCommandCncjob(TclCommandSignaled):
args["endz"]= args["endz"] if "endz" in args else obj.options["endz"] args["endz"]= args["endz"] if "endz" in args else obj.options["endz"]
args["ppname_g"] = args["ppname_g"] if "ppname_g" in args else obj.options["ppname_g"] args["ppname_g"] = args["ppname_g"] if "ppname_g" in args else obj.options["ppname_g"]
args["toolchange"] = True if "toolchange" in args and args["toolchange"] == 1 else False
args["toolchangez"] = args["toolchangez"] if "toolchangez" in args else obj.options["toolchangez"]
args["toolchangexy"] = args["toolchangexy"] if "toolchangexy" in args else obj.options["toolchangexy"]
del args['name'] del args['name']
# HACK !!! Should be solved elsewhere!!! # HACK !!! Should be solved elsewhere!!!

View File

@ -56,7 +56,7 @@ class TclCommandCutout(TclCommand):
name = args['name'] name = args['name']
else: else:
self.app.inform.emit( self.app.inform.emit(
"[warning]The name of the object for which cutout is done is missing. Add it and retry.") "[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
return return
if 'margin' in args: if 'margin' in args:

View File

@ -61,7 +61,7 @@ class TclCommandCutoutAny(TclCommand):
name = args['name'] name = args['name']
else: else:
self.app.inform.emit( self.app.inform.emit(
"[warning]The name of the object for which cutout is done is missing. Add it and retry.") "[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
return return
if 'margin' in args: if 'margin' in args:
@ -91,11 +91,11 @@ class TclCommandCutoutAny(TclCommand):
return "Could not retrieve object: %s" % name return "Could not retrieve object: %s" % name
if 0 in {dia}: if 0 in {dia}:
self.app.inform.emit("[warning]Tool Diameter is zero value. Change it to a positive integer.") self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.")
return "Tool Diameter is zero value. Change it to a positive integer." return "Tool Diameter is zero value. Change it to a positive integer."
if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]: if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]:
self.app.inform.emit("[warning]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. " self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
"Fill in a correct value and retry. ") "Fill in a correct value and retry. ")
return return
@ -129,7 +129,7 @@ class TclCommandCutoutAny(TclCommand):
cutout_obj = self.app.collection.get_by_name(outname) cutout_obj = self.app.collection.get_by_name(outname)
else: else:
self.app.inform.emit("[error]Cancelled. Object type is not supported.") self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
return return
try: try:

View File

@ -47,6 +47,7 @@ class TclCommandDrillcncjob(TclCommandSignaled):
('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'), ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
('toolchange', 'Enable tool changes (example: True).'), ('toolchange', 'Enable tool changes (example: True).'),
('toolchangez', 'Z distance for toolchange (example: 30.0).'), ('toolchangez', 'Z distance for toolchange (example: 30.0).'),
('toolchangexy', 'X, Y coordonates for toolchange in format (x, y) (example: (2.0, 3.1) ).'),
('endz', 'Z distance at job end (example: 30.0).'), ('endz', 'Z distance at job end (example: 30.0).'),
('ppname_e', 'This is the Excellon postprocessor name: case_sensitive, no_quotes'), ('ppname_e', 'This is the Excellon postprocessor name: case_sensitive, no_quotes'),
('outname', 'Name of the resulting Geometry object.'), ('outname', 'Name of the resulting Geometry object.'),
@ -85,7 +86,8 @@ class TclCommandDrillcncjob(TclCommandSignaled):
drillz = args["drillz"] if "drillz" in args else obj.options["drillz"] drillz = args["drillz"] if "drillz" in args else obj.options["drillz"]
job_obj.z_move = args["travelz"] if "travelz" in args else obj.options["travelz"] job_obj.z_move = args["travelz"] if "travelz" in args else obj.options["travelz"]
job_obj.feedrate = args["feedrate"] if "feedrate" in args else obj.options["feedrate"] job_obj.feedrate = args["feedrate"] if "feedrate" in args else obj.options["feedrate"]
job_obj.feedrate_rapid = args["feedrate_rapid"] if "feedrate_rapid" in args else obj.options["feedrate_rapid"] job_obj.feedrate_rapid = args["feedrate_rapid"] \
if "feedrate_rapid" in args else obj.options["feedrate_rapid"]
job_obj.spindlespeed = args["spindlespeed"] if "spindlespeed" in args else None job_obj.spindlespeed = args["spindlespeed"] if "spindlespeed" in args else None
job_obj.pp_excellon_name = args["ppname_e"] if "ppname_e" in args \ job_obj.pp_excellon_name = args["ppname_e"] if "ppname_e" in args \
@ -93,13 +95,15 @@ class TclCommandDrillcncjob(TclCommandSignaled):
toolchange = True if "toolchange" in args and args["toolchange"] == 1 else False toolchange = True if "toolchange" in args and args["toolchange"] == 1 else False
toolchangez = args["toolchangez"] if "toolchangez" in args else obj.options["toolchangez"] toolchangez = args["toolchangez"] if "toolchangez" in args else obj.options["toolchangez"]
job_obj.toolchangexy = args["toolchangexy"] if "toolchangexy" in args else obj.options["toolchangexy"]
endz = args["endz"] if "endz" in args else obj.options["endz"] endz = args["endz"] if "endz" in args else obj.options["endz"]
tools = args["tools"] if "tools" in args else 'all' tools = args["tools"] if "tools" in args else 'all'
opt_type = args["opt_type"] if "opt_type" in args else 'B' opt_type = args["opt_type"] if "opt_type" in args else 'B'
job_obj.generate_from_excellon_by_tool(obj, tools, drillz=drillz, toolchangez=toolchangez, endz=endz, job_obj.generate_from_excellon_by_tool(obj, tools, drillz=drillz, toolchangez=toolchangez,
endz=endz,
toolchange=toolchange, excellon_optimization_type=opt_type) toolchange=toolchange, excellon_optimization_type=opt_type)
job_obj.gcode_parse() job_obj.gcode_parse()
job_obj.create_geometry() job_obj.create_geometry()

View File

@ -37,7 +37,7 @@ class TclCommandListSys(TclCommand):
'args': collections.OrderedDict([ 'args': collections.OrderedDict([
]), ]),
'examples': ['list_sys', 'examples': ['list_sys',
'list_sys ser' 'list_sys ser',
'list_sys gerber', 'list_sys gerber',
'list_sys cncj'] 'list_sys cncj']
} }

View File

@ -57,12 +57,12 @@ class TclCommandOpenGerber(TclCommandSignaled):
gerber_obj.parse_file(filename, follow=follow) gerber_obj.parse_file(filename, follow=follow)
except IOError: except IOError:
app_obj.inform.emit("[error_notcl] Failed to open file: %s " % filename) app_obj.inform.emit("[ERROR_NOTCL] Failed to open file: %s " % filename)
app_obj.progress.emit(0) app_obj.progress.emit(0)
self.raise_tcl_error('Failed to open file: %s' % filename) self.raise_tcl_error('Failed to open file: %s' % filename)
except ParseError as e: except ParseError as e:
app_obj.inform.emit("[error_notcl] Failed to parse file: %s, %s " % (filename, str(e))) app_obj.inform.emit("[ERROR_NOTCL] Failed to parse file: %s, %s " % (filename, str(e)))
app_obj.progress.emit(0) app_obj.progress.emit(0)
self.log.error(str(e)) self.log.error(str(e))
return return

65
tests/new_window_test.py Normal file
View File

@ -0,0 +1,65 @@
import sys
from PyQt5.Qt import *
from PyQt5 import QtGui, QtWidgets
class MyPopup(QWidget):
def __init__(self):
QWidget.__init__(self)
lay = QtWidgets.QVBoxLayout()
self.setLayout(lay)
lay.setContentsMargins(0, 0, 0, 0)
le = QtWidgets.QLineEdit()
le.setText("Abracadabra")
le.setReadOnly(True)
# le.setStyleSheet("QLineEdit { qproperty-frame: false }")
le.setFrame(False)
le.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# lay.addStretch()
but = QtWidgets.QPushButton("OK")
hlay = QtWidgets.QHBoxLayout()
hlay.setContentsMargins(0, 5, 5, 5)
hlay.addStretch()
hlay.addWidget(but)
lay.addWidget(le)
lay.addLayout(hlay)
# def paintEvent(self, e):
# dc = QtGui.QPainter(self)
# dc.drawLine(0, 0, 100, 100)
# dc.drawLine(100, 0, 0, 100)
class MainWindow(QMainWindow):
def __init__(self, *args):
QtWidgets.QMainWindow.__init__(self, *args)
self.cw = QtWidgets.QWidget(self)
self.setCentralWidget(self.cw)
self.btn1 = QtWidgets.QPushButton("Click me", self.cw)
self.btn1.setGeometry(QRect(0, 0, 100, 30))
self.btn1.clicked.connect(self.doit)
self.w = None
def doit(self):
print("Opening a new popup window...")
self.w = MyPopup()
self.w.setGeometry(QRect(100, 100, 400, 200))
self.w.show()
class App(QApplication):
def __init__(self, *args):
QtWidgets.QApplication.__init__(self, *args)
self.main = MainWindow()
# self.lastWindowClosed.connect(self.byebye)
self.main.show()
def byebye(self):
self.exit(0)
def main(args):
global app
app = App(args)
app.exec_()
if __name__ == "__main__":
main(sys.argv)