Merged in test_beta_8.907 (pull request #132)

Test beta 8.907
This commit is contained in:
Marius Stanciu 2019-02-09 15:00:09 +00:00
commit c59da8669b
25 changed files with 2713 additions and 1774 deletions

View File

@ -6,7 +6,6 @@ from FlatCAMApp import App
from multiprocessing import freeze_support
import VisPyPatches
if sys.platform == "win32":
# cx_freeze 'module win32' workaround
import OpenGL.platform.win32
@ -34,5 +33,6 @@ if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
fc = App()
sys.exit(app.exec_())

File diff suppressed because it is too large Load Diff

View File

@ -106,21 +106,21 @@ class BufferSelectionTool(FlatCAMTool):
def on_buffer(self):
buffer_distance = self.buffer_distance_entry.get_value()
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
# I populated the combobox such that the index coincide with the join styles value (which is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
self.draw_app.buffer(buffer_distance, join_style)
def on_buffer_int(self):
buffer_distance = self.buffer_distance_entry.get_value()
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
# I populated the combobox such that the index coincide with the join styles value (which is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
self.draw_app.buffer_int(buffer_distance, join_style)
def on_buffer_ext(self):
buffer_distance = self.buffer_distance_entry.get_value()
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
# I populated the combobox such that the index coincide with the join styles value (which is really an INT)
join_style = self.buffer_corner_cb.currentIndex() + 1
self.draw_app.buffer_ext(buffer_distance, join_style)
@ -583,6 +583,8 @@ class FCCircle(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_circle'
self.start_msg = "Click on CENTER ..."
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
@ -620,6 +622,8 @@ class FCCircle(FCShapeTool):
class FCArc(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_arc'
self.start_msg = "Click on CENTER ..."
# Direction of rotation between point 1 and 2.
@ -808,6 +812,8 @@ class FCRectangle(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_rectangle'
self.start_msg = "Click on 1st corner ..."
def click(self, point):
@ -846,6 +852,8 @@ class FCPolygon(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_polygon'
self.start_msg = "Click on 1st point ..."
def click(self, point):
@ -891,6 +899,8 @@ class FCPath(FCPolygon):
def make(self):
self.geometry = DrawToolShape(LineString(self.points))
self.name = 'fc_path'
self.draw_app.in_action = False
self.complete = True
self.draw_app.app.inform.emit("[success]Done. Path completed.")
@ -912,6 +922,8 @@ class FCPath(FCPolygon):
class FCSelect(DrawTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_select'
self.storage = self.draw_app.storage
# self.shape_buffer = self.draw_app.shape_buffer
# self.selected = self.draw_app.selected
@ -989,6 +1001,7 @@ class FCSelect(DrawTool):
class FCDrillSelect(DrawTool):
def __init__(self, exc_editor_app):
DrawTool.__init__(self, exc_editor_app)
self.name = 'fc_drill_select'
self.exc_editor_app = exc_editor_app
self.storage = self.exc_editor_app.storage_dict
@ -1146,6 +1159,8 @@ class FCDrillSelect(DrawTool):
class FCMove(FCShapeTool):
def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app)
self.name = 'fc_move'
# self.shape_buffer = self.draw_app.shape_buffer
self.origin = None
self.destination = None
@ -1211,6 +1226,9 @@ class FCMove(FCShapeTool):
class FCCopy(FCMove):
def __init__(self, draw_app):
FCMove.__init__(self, draw_app)
self.name = 'fc_copy'
def make(self):
# Create new geometry
@ -1225,6 +1243,8 @@ class FCCopy(FCMove):
class FCText(FCShapeTool):
def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app)
self.name = 'fc_text'
# self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app
self.app = draw_app.app
@ -1275,6 +1295,8 @@ class FCText(FCShapeTool):
class FCBuffer(FCShapeTool):
def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app)
self.name = 'fc_buffer'
# self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app
self.app = draw_app.app
@ -1341,6 +1363,8 @@ class FCBuffer(FCShapeTool):
class FCPaint(FCShapeTool):
def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app)
self.name = 'fc_paint'
# self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app
self.app = draw_app.app
@ -1355,6 +1379,7 @@ class FCPaint(FCShapeTool):
class FCRotate(FCShapeTool):
def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app)
self.name = 'fc_rotate'
geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y))
@ -1366,7 +1391,6 @@ class FCRotate(FCShapeTool):
def set_origin(self, origin):
self.origin = origin
def make(self):
# Create new geometry
# dx = self.origin[0]
@ -1382,9 +1406,9 @@ class FCRotate(FCShapeTool):
#self.draw_app.select_tool("select")
def on_key(self, key):
if key == 'Enter':
if self.complete == True:
self.make()
if key == 'Enter' or key == QtCore.Qt.Key_Enter:
self.make()
return "Done"
def click(self, point):
self.make()
@ -1408,6 +1432,7 @@ class FCDrillAdd(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_add'
self.selected_dia = None
try:
@ -1443,11 +1468,17 @@ class FCDrillAdd(FCShapeTool):
return DrawToolUtilityShape(self.util_shape(data))
def util_shape(self, point):
if point[0] is None and point[1] is None:
point_x = self.draw_app.x
point_y = self.draw_app.y
else:
point_x = point[0]
point_y = point[1]
start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1])
stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1])
start_vert_line = (point[0], (point[1] - (self.selected_dia / 2)))
stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2)))
start_hor_line = ((point_x - (self.selected_dia / 2)), point_y)
stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y)
start_vert_line = (point_x, (point_y - (self.selected_dia / 2)))
stop_vert_line = (point_x, (point_y + (self.selected_dia / 2)))
return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)])
@ -1473,6 +1504,7 @@ class FCDrillArray(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_array'
self.draw_app.array_frame.show()
@ -1558,28 +1590,27 @@ class FCDrillArray(FCShapeTool):
return
if self.drill_array == 'Linear':
# if self.origin is None:
# self.origin = (0, 0)
#
# dx = data[0] - self.origin[0]
# dy = data[1] - self.origin[1]
dx = data[0]
dy = data[1]
if data[0] is None and data[1] is None:
dx = self.draw_app.x
dy = self.draw_app.y
else:
dx = data[0]
dy = data[1]
geo_list = []
geo = None
self.points = data
self.points = [dx, dy]
for item in range(self.drill_array_size):
if self.drill_axis == 'X':
geo = self.util_shape(((data[0] + (self.drill_pitch * item)), data[1]))
geo = self.util_shape(((dx + (self.drill_pitch * item)), dy))
if self.drill_axis == 'Y':
geo = self.util_shape((data[0], (data[1] + (self.drill_pitch * item))))
geo = self.util_shape((dx, (dy + (self.drill_pitch * item))))
if self.drill_axis == 'A':
x_adj = self.drill_pitch * math.cos(math.radians(self.drill_linear_angle))
y_adj = self.drill_pitch * math.sin(math.radians(self.drill_linear_angle))
geo = self.util_shape(
((data[0] + (x_adj * item)), (data[1] + (y_adj * item)))
((dx + (x_adj * item)), (dy + (y_adj * item)))
)
if static is None or static is False:
@ -1592,16 +1623,30 @@ class FCDrillArray(FCShapeTool):
self.last_dy = dy
return DrawToolUtilityShape(geo_list)
else:
if data[0] is None and data[1] is None:
cdx = self.draw_app.x
cdy = self.draw_app.y
else:
cdx = data[0]
cdy = data[1]
if len(self.pt) > 0:
temp_points = [x for x in self.pt]
temp_points.append(data)
temp_points.append([cdx, cdy])
return DrawToolUtilityShape(LineString(temp_points))
def util_shape(self, point):
start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1])
stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1])
start_vert_line = (point[0], (point[1] - (self.selected_dia / 2)))
stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2)))
if point[0] is None and point[1] is None:
point_x = self.draw_app.x
point_y = self.draw_app.y
else:
point_x = point[0]
point_y = point[1]
start_hor_line = ((point_x - (self.selected_dia / 2)), point_y)
stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y)
start_vert_line = (point_x, (point_y - (self.selected_dia / 2)))
stop_vert_line = (point_x, (point_y + (self.selected_dia / 2)))
return MultiLineString([(start_hor_line, stop_hor_line), (start_vert_line, stop_vert_line)])
@ -1656,10 +1701,12 @@ class FCDrillArray(FCShapeTool):
self.draw_app.array_frame.hide()
return
class FCDrillResize(FCShapeTool):
class FCDrillResize(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_resize'
self.draw_app.app.inform.emit("Click on the Drill(s) to resize ...")
self.resize_dia = None
self.draw_app.resize_frame.show()
@ -1761,6 +1808,8 @@ class FCDrillResize(FCShapeTool):
class FCDrillMove(FCShapeTool):
def __init__(self, draw_app):
DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_move'
# self.shape_buffer = self.draw_app.shape_buffer
self.origin = None
self.destination = None
@ -1850,6 +1899,9 @@ class FCDrillMove(FCShapeTool):
class FCDrillCopy(FCDrillMove):
def __init__(self, draw_app):
FCDrillMove.__init__(self, draw_app)
self.name = 'fc_drill_copy'
def make(self):
# Create new geometry
@ -1977,6 +2029,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.geo_key_modifiers = None
self.x = None # Current mouse cursor pos
self.y = None
# Current snapped mouse pos
self.snap_x = None
self.snap_y = None
@ -2083,16 +2136,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.geo_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("theme"):
theme = settings.value('theme', type=str)
if theme == 'standard':
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == '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.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
elif theme == 'compact':
elif layout == 'compact':
# self.app.ui.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
@ -2134,35 +2187,30 @@ class FlatCAMGeoEditor(QtCore.QObject):
# make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor
self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.keyPressed.disconnect()
self.app.collection.view.clicked.disconnect()
self.canvas.vis_connect('mouse_press', self.on_canvas_click)
self.canvas.vis_connect('mouse_move', self.on_canvas_move)
self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
self.canvas.vis_connect('key_press', self.on_canvas_key)
self.canvas.vis_connect('key_release', self.on_canvas_key_release)
def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
self.canvas.vis_disconnect('key_press', self.on_canvas_key)
self.canvas.vis_disconnect('key_release', self.on_canvas_key_release)
# we restore the key and mouse control to FlatCAMApp method
self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.keyPressed.connect(self.app.collection.on_key)
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
def add_shape(self, shape):
@ -2572,211 +2620,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.tool_shape.redraw()
def on_canvas_key(self, event):
"""
event.key has the key.
:param event:
:return:
"""
self.key = event.key.name
self.geo_key_modifiers = QtWidgets.QApplication.keyboardModifiers()
if self.geo_key_modifiers == Qt.ControlModifier:
# save (update) the current geometry and return to the App
if self.key == 'S':
self.app.editor2object()
return
# toggle the measurement tool
if self.key == 'M':
self.app.measurement_tool.run()
return
# Finish the current action. Use with tools that do not
# complete automatically, like a polygon or path.
if event.key.name == 'Enter':
if isinstance(self.active_tool, FCShapeTool):
self.active_tool.click(self.snap(self.x, self.y))
self.active_tool.make()
if self.active_tool.complete:
self.on_shape_complete()
self.app.inform.emit("[success]Done.")
# automatically make the selection tool active after completing current action
self.select_tool('select')
return
# Abort the current action
if event.key.name == 'Escape':
# TODO: ...?
# self.on_tool_select("select")
self.app.inform.emit("[WARNING_NOTCL]Cancelled.")
self.delete_utility_geometry()
self.replot()
# self.select_btn.setChecked(True)
# self.on_tool_select('select')
self.select_tool('select')
return
# Delete selected object
if event.key.name == 'Delete':
self.delete_selected()
self.replot()
# Move
if event.key.name == 'Space':
self.app.ui.geo_rotate_btn.setChecked(True)
self.on_tool_select('rotate')
self.active_tool.set_origin(self.snap(self.x, self.y))
if event.key == '1':
self.app.on_zoom_fit(None)
if event.key == '2':
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
if event.key == '3':
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
# Arc Tool
if event.key.name == 'A':
self.select_tool('arc')
# Buffer
if event.key.name == 'B':
self.select_tool('buffer')
# Copy
if event.key.name == 'C':
self.app.ui.geo_copy_btn.setChecked(True)
self.on_tool_select('copy')
self.active_tool.set_origin(self.snap(self.x, self.y))
self.app.inform.emit("Click on target point.")
# Substract Tool
if event.key.name == 'E':
if self.get_selected() is not None:
self.intersection()
else:
msg = "Please select geometry items \n" \
"on which to perform Intersection Tool."
messagebox =QtWidgets.QMessageBox()
messagebox.setText(msg)
messagebox.setWindowTitle("Warning")
messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
messagebox.exec_()
# Grid Snap
if event.key.name == 'G':
self.app.ui.grid_snap_btn.trigger()
# make sure that the cursor shape is enabled/disabled, too
if self.options['grid_snap'] is True:
self.app.app_cursor.enabled = True
else:
self.app.app_cursor.enabled = False
# Paint
if event.key.name == 'I':
self.select_tool('paint')
# Corner Snap
if event.key.name == 'K':
self.on_corner_snap()
# Move
if event.key.name == 'M':
self.on_move_click()
# Polygon Tool
if event.key.name == 'N':
self.select_tool('polygon')
# Circle Tool
if event.key.name == 'O':
self.select_tool('circle')
# Path Tool
if event.key.name == 'P':
self.select_tool('path')
# Rectangle Tool
if event.key.name == 'R':
self.select_tool('rectangle')
# Substract Tool
if event.key.name == 'S':
if self.get_selected() is not None:
self.subtract()
else:
msg = "Please select geometry items \n" \
"on which to perform Substraction Tool."
messagebox =QtWidgets.QMessageBox()
messagebox.setText(msg)
messagebox.setWindowTitle("Warning")
messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
messagebox.exec_()
# Add Text Tool
if event.key.name == 'T':
self.select_tool('text')
# Substract Tool
if event.key.name == 'U':
if self.get_selected() is not None:
self.union()
else:
msg = "Please select geometry items \n" \
"on which to perform union."
messagebox =QtWidgets.QMessageBox()
messagebox.setText(msg)
messagebox.setWindowTitle("Warning")
messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
messagebox.exec_()
# Cut Action Tool
if event.key.name == 'X':
if self.get_selected() is not None:
self.cutpath()
else:
msg = 'Please first select a geometry item to be cutted\n' \
'then select the geometry item that will be cutted\n' \
'out of the first item. In the end press ~X~ key or\n' \
'the toolbar button.' \
messagebox =QtWidgets.QMessageBox()
messagebox.setText(msg)
messagebox.setWindowTitle("Warning")
messagebox.setWindowIcon(QtGui.QIcon('share/warning.png'))
messagebox.setStandardButtons(QtWidgets.QMessageBox.Ok)
messagebox.setDefaultButton(QtWidgets.QMessageBox.Ok)
messagebox.exec_()
# Propagate to tool
response = None
if self.active_tool is not None:
response = self.active_tool.on_key(event.key)
if response is not None:
self.app.inform.emit(response)
# Show Shortcut list
if event.key.name == '`':
self.app.on_shortcut_list()
def on_canvas_key_release(self, event):
self.key = None
def on_delete_btn(self):
self.delete_selected()
self.replot()
@ -3465,7 +3308,8 @@ class FlatCAMExcEditor(QtCore.QObject):
grid1.addWidget(addtool_entry_lbl, 0, 0)
hlay = QtWidgets.QHBoxLayout()
self.addtool_entry = LengthEntry()
self.addtool_entry = FCEntry()
self.addtool_entry.setValidator(QtGui.QDoubleValidator(0.0001, 99.9999, 4))
hlay.addWidget(self.addtool_entry)
self.addtool_btn = QtWidgets.QPushButton('Add Tool')
@ -3873,7 +3717,7 @@ class FlatCAMExcEditor(QtCore.QObject):
if self.units == "IN":
self.addtool_entry.set_value(0.039)
else:
self.addtool_entry.set_value(1)
self.addtool_entry.set_value(1.00)
sort_temp = []
@ -4057,9 +3901,21 @@ class FlatCAMExcEditor(QtCore.QObject):
# we reactivate the signals after the after the tool adding as we don't need to see the tool been populated
self.tools_table_exc.itemChanged.connect(self.on_tool_edit)
def on_tool_add(self):
def on_tool_add(self, tooldia=None):
self.is_modified = True
tool_dia = float(self.addtool_entry.get_value())
if tooldia:
tool_dia = tooldia
else:
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 not in self.olddia_newdia:
storage_elem = FlatCAMGeoEditor.make_storage()
@ -4224,16 +4080,16 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.exc_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM")
if settings.contains("theme"):
theme = settings.value('theme', type=str)
if theme == 'standard':
if settings.contains("layout"):
layout = settings.value('layout', type=str)
if layout == '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.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.setVisible(False)
elif theme == 'compact':
elif layout == 'compact':
# self.app.ui.exc_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False)
@ -4277,35 +4133,27 @@ class FlatCAMExcEditor(QtCore.QObject):
# make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor
self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.keyPressed.disconnect()
self.app.collection.view.clicked.disconnect()
self.canvas.vis_connect('mouse_press', self.on_canvas_click)
self.canvas.vis_connect('mouse_move', self.on_canvas_move)
self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
self.canvas.vis_connect('key_press', self.on_canvas_key)
self.canvas.vis_connect('key_release', self.on_canvas_key_release)
def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
self.canvas.vis_disconnect('mouse_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release)
self.canvas.vis_disconnect('key_press', self.on_canvas_key)
self.canvas.vis_disconnect('key_release', self.on_canvas_key_release)
# we restore the key and mouse control to FlatCAMApp method
self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
self.app.plotcanvas.vis_connect('mouse_double_click', self.app.on_double_click_over_plot)
self.app.collection.view.keyPressed.connect(self.app.collection.on_key)
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
def clear(self):
@ -4374,6 +4222,9 @@ class FlatCAMExcEditor(QtCore.QObject):
self.replot()
# add a first tool in the Tool Table
self.on_tool_add(tooldia=1.00)
def update_fcexcellon(self, exc_obj):
"""
Create a new Excellon object that contain the edited content of the source Excellon object
@ -4871,145 +4722,6 @@ class FlatCAMExcEditor(QtCore.QObject):
# Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20)
def on_canvas_key(self, event):
"""
event.key has the key.
:param event:
:return:
"""
self.key = event.key.name
self.modifiers = QtWidgets.QApplication.keyboardModifiers()
if self.modifiers == Qt.ControlModifier:
# save (update) the current geometry and return to the App
if self.key == 'S':
self.app.editor2object()
return
# toggle the measurement tool
if self.key == 'M':
self.app.measurement_tool.run()
return
# Abort the current action
if event.key.name == 'Escape':
# TODO: ...?
# self.on_tool_select("select")
self.app.inform.emit("[WARNING_NOTCL]Cancelled.")
self.delete_utility_geometry()
self.replot()
# self.select_btn.setChecked(True)
# self.on_tool_select('select')
self.select_tool('select')
return
# Delete selected object
if event.key.name == 'Delete':
self.launched_from_shortcuts = True
if self.selected:
self.delete_selected()
self.replot()
else:
self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to delete.")
return
if event.key == '1':
self.launched_from_shortcuts = True
self.app.on_zoom_fit(None)
if event.key == '2':
self.launched_from_shortcuts = True
self.app.plotcanvas.zoom(1 / self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
if event.key == '3':
self.launched_from_shortcuts = True
self.app.plotcanvas.zoom(self.app.defaults['zoom_ratio'], [self.snap_x, self.snap_y])
# Add Array of Drill Hole Tool
if event.key.name == 'A':
self.launched_from_shortcuts = True
self.app.inform.emit("Click on target point.")
self.app.ui.add_drill_array_btn.setChecked(True)
self.select_tool('add_array')
return
# Copy
if event.key.name == 'C':
self.launched_from_shortcuts = True
if self.selected:
self.app.inform.emit("Click on target point.")
self.app.ui.copy_drill_btn.setChecked(True)
self.on_tool_select('copy')
self.active_tool.set_origin((self.snap_x, self.snap_y))
else:
self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to copy.")
return
# Add Drill Hole Tool
if event.key.name == 'D':
self.launched_from_shortcuts = True
self.app.inform.emit("Click on target point.")
self.app.ui.add_drill_btn.setChecked(True)
self.select_tool('add')
return
# Grid Snap
if event.key.name == 'G':
self.launched_from_shortcuts = True
# make sure that the cursor shape is enabled/disabled, too
if self.options['grid_snap'] is True:
self.app.app_cursor.enabled = False
else:
self.app.app_cursor.enabled = True
self.app.ui.grid_snap_btn.trigger()
return
# Corner Snap
if event.key.name == 'K':
self.launched_from_shortcuts = True
self.app.ui.corner_snap_btn.trigger()
return
# Move
if event.key.name == 'M':
self.launched_from_shortcuts = True
if self.selected:
self.app.inform.emit("Click on target point.")
self.app.ui.move_drill_btn.setChecked(True)
self.on_tool_select('move')
self.active_tool.set_origin((self.snap_x, self.snap_y))
else:
self.app.inform.emit("[WARNING_NOTCL]Cancelled. Nothing selected to move.")
return
# Resize Tool
if event.key.name == 'R':
self.launched_from_shortcuts = True
self.select_tool('resize')
return
# Select Tool
if event.key.name == 'S':
self.launched_from_shortcuts = True
self.select_tool('select')
return
# Propagate to tool
response = None
if self.active_tool is not None:
response = self.active_tool.on_key(event.key)
if response is not None:
self.app.inform.emit(response)
# Show Shortcut list
if event.key.name == '`':
self.app.on_shortcut_list()
return
def on_canvas_key_release(self, event):
self.key = None

File diff suppressed because it is too large Load Diff

View File

@ -388,7 +388,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
def __init__(self, name):
Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"])
Gerber.__init__(self, steps_per_circle=int(self.app.defaults["gerber_circle_steps"]))
FlatCAMObj.__init__(self, name)
self.kind = "gerber"
@ -480,7 +480,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry)
bounding_box = self.solid_geometry.envelope.buffer(self.options["noncoppermargin"])
bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"]))
if not self.options["noncopperrounded"]:
bounding_box = bounding_box.envelope
non_copper = bounding_box.difference(self.solid_geometry)
@ -497,7 +497,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry)
# Bounding box with rounded corners
bounding_box = self.solid_geometry.envelope.buffer(self.options["bboxmargin"])
bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"]))
if not self.options["bboxrounded"]: # Remove rounded corners
bounding_box = bounding_box.envelope
geo_obj.solid_geometry = bounding_box
@ -557,7 +557,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def follow_init(follow_obj, app):
# Propagate options
follow_obj.options["cnctooldia"] = self.options["isotooldia"]
follow_obj.options["cnctooldia"] = float(self.options["isotooldia"])
follow_obj.solid_geometry = self.solid_geometry
# TODO: Do something if this is None. Offer changing name?
@ -579,11 +579,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
:return: None
"""
if dia is None:
dia = self.options["isotooldia"]
dia = float(self.options["isotooldia"])
if passes is None:
passes = int(self.options["isopasses"])
if overlap is None:
overlap = self.options["isooverlap"]
overlap = float(self.options["isooverlap"])
if combine is None:
combine = self.options["combine_passes"]
else:
@ -638,7 +638,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# TODO: This is ugly. Create way to pass data into init function.
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = self.options["isotooldia"]
geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
geo_obj.solid_geometry = []
for i in range(passes):
iso_offset = (((2 * i + 1) / 2.0) * dia) - (i * overlap * dia)
@ -693,7 +693,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# TODO: This is ugly. Create way to pass data into init function.
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = self.options["isotooldia"]
geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
# if milling type is climb then the move is counter-clockwise around features
if milling_type == 'cl':
@ -753,8 +753,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
factor = Gerber.convert_units(self, units)
self.options['isotooldia'] *= factor
self.options['bboxmargin'] *= factor
self.options['isotooldia'] = float(self.options['isotooldia']) * factor
self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor
def plot(self, **kwargs):
"""
@ -833,7 +833,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
optionChanged = QtCore.pyqtSignal(str)
def __init__(self, name):
Excellon.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"])
Excellon.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"]))
FlatCAMObj.__init__(self, name)
self.kind = "excellon"
@ -1458,7 +1458,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
outname = self.options["name"] + "_mill"
if tooldia is None:
tooldia = self.options["tooldia"]
tooldia = float(self.options["tooldia"])
# Sort tools by diameter. items() -> [('name', diameter), ...]
# sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
@ -1545,7 +1545,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
outname = self.options["name"] + "_mill"
if tooldia is None:
tooldia = self.options["slot_tooldia"]
tooldia = float(self.options["slot_tooldia"])
# Sort tools by diameter. items() -> [('name', diameter), ...]
# sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3
@ -1564,7 +1564,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
return False, "Error: No tools."
for tool in tools:
if tooldia > self.tools[tool]["C"]:
# I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
adj_toolstable_tooldia = float('%.4f' % float(tooldia))
adj_file_tooldia = float('%.4f' % float(self.tools[tool]["C"]))
if adj_toolstable_tooldia > adj_file_tooldia + 0.0001:
self.app.inform.emit("[ERROR_NOTCL] Milling tool for SLOTS is larger than hole size. Cancelled.")
return False, "Error: Milling tool is larger than hole."
@ -1590,20 +1593,25 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero"
for slot in self.slots:
if slot['tool'] in tools:
buffer_value = self.tools[slot['tool']]["C"] / 2 - tooldia / 2
toolstable_tool = float('%.4f' % float(tooldia))
file_tool = float('%.4f' % float(self.tools[tool]["C"]))
# I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse
# for the file_tool (tooldia actually)
buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001
if buffer_value == 0:
start = slot['start']
stop = slot['stop']
lines_string = LineString([start, stop])
poly = lines_string.buffer(0.0000001, self.geo_steps_per_circle).exterior
poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior
geo_obj.solid_geometry.append(poly)
else:
start = slot['start']
stop = slot['stop']
lines_string = LineString([start, stop])
poly = lines_string.buffer(buffer_value, self.geo_steps_per_circle).exterior
poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior
geo_obj.solid_geometry.append(poly)
if use_thread:
@ -1685,15 +1693,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
job_obj.options['ppname_e'] = pp_excellon_name
app_obj.progress.emit(20)
job_obj.z_cut = self.options["drillz"]
job_obj.z_move = self.options["travelz"]
job_obj.feedrate = self.options["feedrate"]
job_obj.feedrate_rapid = self.options["feedrate_rapid"]
job_obj.spindlespeed = self.options["spindlespeed"]
job_obj.z_cut = float(self.options["drillz"])
job_obj.z_move = float(self.options["travelz"])
job_obj.feedrate = float(self.options["feedrate"])
job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None
job_obj.dwell = self.options["dwell"]
job_obj.dwelltime = self.options["dwelltime"]
job_obj.dwelltime = float(self.options["dwelltime"])
job_obj.pp_excellon_name = pp_excellon_name
job_obj.toolchange_xy = self.app.defaults["excellon_toolchangexy"]
job_obj.toolchange_xy_type = "excellon"
job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
@ -1729,14 +1738,18 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# job_obj.options["tooldia"] =
tools_csv = ','.join(tools)
job_obj.generate_from_excellon_by_tool(self, tools_csv,
drillz=self.options['drillz'],
toolchange=self.options["toolchange"],
toolchangez=self.options["toolchangez"],
startz=self.options["startz"],
endz=self.options["endz"],
excellon_optimization_type=self.options["optimization_type"])
ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv,
drillz=float(self.options['drillz']),
toolchange=float(self.options["toolchange"]),
toolchangexy=self.app.defaults["excellon_toolchangexy"],
toolchangez=float(self.options["toolchangez"]),
startz=float(self.options["startz"]) if
self.options["startz"] else None,
endz=float(self.options["endz"]),
excellon_optimization_type=self.app.defaults[
"excellon_optimization_type"])
if ret_val == 'fail':
return 'fail'
app_obj.progress.emit(50)
job_obj.gcode_parse()
@ -1772,11 +1785,11 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def convert_units(self, units):
factor = Excellon.convert_units(self, units)
self.options['drillz'] *= factor
self.options['travelz'] *= factor
self.options['feedrate'] *= factor
self.options['feedrate_rapid'] *= factor
self.options['toolchangez'] *= factor
self.options['drillz'] = float(self.options['drillz']) * factor
self.options['travelz'] = float(self.options['travelz']) * factor
self.options['feedrate'] = float(self.options['feedrate']) * factor
self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
self.options['toolchangez'] = float(self.options['toolchangez']) * factor
if self.app.defaults["excellon_toolchangexy"] == '':
self.options['toolchangexy'] = "0.0, 0.0"
@ -1791,8 +1804,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
if self.options['startz'] is not None:
self.options['startz'] *= factor
self.options['endz'] *= factor
self.options['startz'] = float(self.options['startz']) * factor
self.options['endz'] = float(self.options['endz']) * factor
def plot(self):
@ -1967,7 +1980,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
def __init__(self, name):
FlatCAMObj.__init__(self, name)
Geometry.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"])
Geometry.__init__(self, geo_steps_per_circle=int(self.app.defaults["geometry_circle_steps"]))
self.kind = "geometry"
@ -2260,7 +2273,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if not self.tools:
self.tools.update({
self.tooluid: {
'tooldia': self.options["cnctooldia"],
'tooldia': float(self.options["cnctooldia"]),
'offset': 'Path',
'offset_value': 0.0,
'type': 'Rough',
@ -2805,8 +2818,28 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.cutz_entry.setDisabled(False)
def update_cutz(self):
vdia = float(self.ui.tipdia_entry.get_value())
half_vangle = float(self.ui.tipangle_entry.get_value()) / 2
try:
vdia = float(self.ui.tipdia_entry.get_value())
except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return
try:
vdia = float(self.ui.tipdia_entry.get_value().replace(',', '.'))
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
try:
half_vangle = float(self.ui.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:
half_vangle = float(self.ui.tipangle_entry.get_value().replace(',', '.')) / 2
except ValueError:
self.app.inform.emit("[ERROR_NOTCL]Wrong value format entered, "
"use a number.")
return
row = self.ui.geo_tools_table.currentRow()
tool_uid = int(self.ui.geo_tools_table.item(row, 5).text())
@ -3125,10 +3158,18 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
xmin = self.options['xmin']
ymin = self.options['ymin']
xmax = self.options['xmax']
ymax = self.options['ymax']
try:
xmin = self.options['xmin']
ymin = self.options['ymin']
xmax = self.options['xmax']
ymax = self.options['ymax']
except Exception as e:
log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e))
msg = "[ERROR] An internal error has ocurred. See shell.\n"
msg += 'FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s' % str(e)
msg += traceback.format_exc()
self.app.inform.emit(msg)
return
# Object initialization function for app.new_object()
# RUNNING ON SEPARATE THREAD!
@ -3349,6 +3390,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
job_obj.multigeo = True
job_obj.cnc_tools.clear()
job_obj.options['xmin'] = xmin
job_obj.options['ymin'] = ymin
job_obj.options['xmax'] = xmax
job_obj.options['ymax'] = ymax
try:
job_obj.z_pdepth = float(self.options["z_pdepth"])
except ValueError:
@ -3591,36 +3637,36 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
:return: None
"""
tooldia = tooldia if tooldia else self.options["cnctooldia"]
outname = outname if outname is not None else self.options["name"]
tooldia = tooldia if tooldia else float(self.options["cnctooldia"])
outname = outname if outname is not None else float(self.options["name"])
z_cut = z_cut if z_cut is not None else self.options["cutz"]
z_move = z_move if z_move is not None else self.options["travelz"]
z_cut = z_cut if z_cut is not None else float(self.options["cutz"])
z_move = z_move if z_move is not None else float(self.options["travelz"])
feedrate = feedrate if feedrate is not None else self.options["feedrate"]
feedrate_z = feedrate_z if feedrate_z is not None else self.options["feedrate_z"]
feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else self.options["feedrate_rapid"]
feedrate = feedrate if feedrate is not None else float(self.options["feedrate"])
feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"])
feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"])
multidepth = multidepth if multidepth is not None else self.options["multidepth"]
depthperpass = depthperpass if depthperpass is not None else self.options["depthperpass"]
depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"])
segx = segx if segx is not None else float(self.app.defaults['geometry_segx'])
segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
extracut = extracut if extracut is not None else self.options["extracut"]
startz = startz if startz is not None else self.options["startz"]
endz = endz if endz is not None else self.options["endz"]
extracut = extracut if extracut is not None else float(self.options["extracut"])
startz = startz if startz is not None else float(self.options["startz"])
endz = endz if endz is not None else float(self.options["endz"])
toolchangez = toolchangez if toolchangez else self.options["toolchangez"]
toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"])
toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
toolchange = toolchange if toolchange else self.options["toolchange"]
offset = offset if offset else 0.0
# int or None.
spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed']
spindlespeed = spindlespeed if spindlespeed else int(self.options['spindlespeed'])
dwell = dwell if dwell else self.options["dwell"]
dwelltime = dwelltime if dwelltime else self.options["dwelltime"]
dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"])
ppname_g = ppname_g if ppname_g else self.options["ppname_g"]
@ -3803,19 +3849,19 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
factor = Geometry.convert_units(self, units)
self.options['cutz'] *= factor
self.options['depthperpass'] *= factor
self.options['travelz'] *= factor
self.options['feedrate'] *= factor
self.options['feedrate_z'] *= factor
self.options['feedrate_rapid'] *= factor
self.options['endz'] *= factor
self.options['cutz'] = float(self.options['cutz']) * factor
self.options['depthperpass'] = float(self.options['depthperpass']) * factor
self.options['travelz'] = float(self.options['travelz']) * factor
self.options['feedrate'] = float(self.options['feedrate']) * factor
self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor
self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
self.options['endz'] = float(self.options['endz']) * factor
# self.options['cnctooldia'] *= factor
# self.options['painttooldia'] *= factor
# self.options['paintmargin'] *= factor
# self.options['paintoverlap'] *= factor
self.options["toolchangez"] *= factor
self.options["toolchangez"] = float(self.options["toolchangez"]) * factor
if self.app.defaults["geometry_toolchangexy"] == '':
self.options['toolchangexy'] = "0.0, 0.0"
@ -3830,7 +3876,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
if self.options['startz'] is not None:
self.options['startz'] *= factor
self.options['startz'] = float(self.options['startz']) * factor
param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
'endz', 'toolchangez']
@ -4015,7 +4061,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia,
spindlespeed=spindlespeed, steps_per_circle=self.app.defaults["cncjob_steps_per_circle"])
spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"]))
FlatCAMObj.__init__(self, name)
@ -4212,7 +4258,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.to_form()
# set the kind of geometries are plotted by default with plot2() from camlib.CNCJob
self.ui.cncplot_method_combo.set_value('all')
self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"])
self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
@ -4263,13 +4309,16 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
"G-Code Files (*.g-code);;All Files (*.*)"
try:
filename = str(QtWidgets.QFileDialog.getSaveFileName(
dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name)
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
caption="Export Machine Code ...",
directory=self.app.get_last_save_folder() + '/' + name,
directory=dir_file_to_save,
filter=_filter_
)[0])
)
except TypeError:
filename = str(QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)[0])
filename, _ = QtWidgets.QFileDialog.getSaveFileName(caption="Export Machine Code ...", filter=_filter_)
filename = str(filename)
if filename == '':
self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...")
@ -4301,9 +4350,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.app.ui.code_editor.clear()
# then append the text from GCode to the text editor
for line in self.app.gcode_edited:
proc_line = str(line).strip('\n')
self.app.ui.code_editor.append(proc_line)
try:
for line in self.app.gcode_edited:
proc_line = str(line).strip('\n')
self.app.ui.code_editor.append(proc_line)
except Exception as e:
log.debug('FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e))
self.app.inform.emit('[ERROR]FlatCAMCNNJob.on_modifygcode_button_click() -->%s' % str(e))
return
self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
@ -4472,6 +4526,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
elif to_file is False:
# Just for adding it to the recent files list.
self.app.file_opened.emit("cncjob", filename)
self.app.file_saved.emit("cncjob", filename)
self.app.inform.emit("[success] Saved to: " + filename)
else:
@ -4549,7 +4604,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
try:
if self.multitool is False: # single tool usage
self.plot2(tooldia=self.options["tooldia"], obj=self, visible=visible, kind=kind)
self.plot2(tooldia=float(self.options["tooldia"]), obj=self, visible=visible, kind=kind)
else:
# multiple tools usage
for tooluid_key in self.cnc_tools:
@ -4564,7 +4619,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
def convert_units(self, units):
factor = CNCjob.convert_units(self, units)
FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_units()")
self.options["tooldia"] *= factor
self.options["tooldia"] = float(self.options["tooldia"]) * factor
param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
'endz', 'toolchangez']

View File

@ -83,6 +83,9 @@ class FlatCAMTool(QtWidgets.QWidget):
# Put ourself in the GUI
self.app.ui.tool_scroll_area.setWidget(self)
# Set the tool name as the widget object name
self.app.ui.tool_scroll_area.widget().setObjectName(self.toolName)
# Switch notebook to tool page
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)

View File

@ -1,14 +1,18 @@
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QTextEdit, QCompleter, QAction
from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor
from copy import copy
import re
import logging
import html
log = logging.getLogger('base')
EDIT_SIZE_HINT = 70
class RadioSet(QtWidgets.QWidget):
activated_custom = QtCore.pyqtSignal()
@ -229,7 +233,6 @@ class FloatEntry(QtWidgets.QLineEdit):
else:
self.setText("")
def sizeHint(self):
default_hint_size = super(FloatEntry, self).sizeHint()
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
@ -347,7 +350,7 @@ class FCEntry2(FCEntry):
self.readyToEdit = True
def set_value(self, val):
self.setText('%.5f' % float(val))
self.setText('%.4f' % float(val))
class EvalEntry(QtWidgets.QLineEdit):
@ -470,6 +473,7 @@ class FCTextAreaRich(QtWidgets.QTextEdit):
default_hint_size = super(FCTextAreaRich, self).sizeHint()
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
class FCComboBox(QtWidgets.QComboBox):
def __init__(self, parent=None):
super(FCComboBox, self).__init__(parent)
@ -490,6 +494,10 @@ class FCInputDialog(QtWidgets.QInputDialog):
super(FCInputDialog, self).__init__(parent)
self.allow_empty = ok
self.empty_val = val
self.val = 0.0
self.ok = ''
if title is None:
self.title = 'title'
else:
@ -511,9 +519,8 @@ class FCInputDialog(QtWidgets.QInputDialog):
else:
self.decimals = decimals
def get_value(self):
self.val,self.ok = self.getDouble(self, self.title, self.text, min=self.min,
self.val, self.ok = self.getDouble(self, self.title, self.text, min=self.min,
max=self.max, decimals=self.decimals)
return [self.val, self.ok]
@ -1218,3 +1225,158 @@ class Dialog_box(QtWidgets.QWidget):
self.location, self.ok = dialog_box.getText(self, title, label)
class _BrowserTextEdit(QTextEdit):
def __init__(self, version):
QTextEdit.__init__(self)
self.menu = None
self.version = version
def contextMenuEvent(self, event):
self.menu = self.createStandardContextMenu(event.pos())
clear_action = QAction("Clear", self)
clear_action.setShortcut(QKeySequence(Qt.Key_Delete)) # it's not working, the shortcut
self.menu.addAction(clear_action)
clear_action.triggered.connect(self.clear)
self.menu.exec_(event.globalPos())
def clear(self):
QTextEdit.clear(self)
text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version
text = html.escape(text)
text = text.replace('\n', '<br/>')
self.moveCursor(QTextCursor.End)
self.insertHtml(text)
class _ExpandableTextEdit(QTextEdit):
"""
Class implements edit line, which expands themselves automatically
"""
historyNext = pyqtSignal()
historyPrev = pyqtSignal()
def __init__(self, termwidget, *args):
QTextEdit.__init__(self, *args)
self.setStyleSheet("font: 9pt \"Courier\";")
self._fittedHeight = 1
self.textChanged.connect(self._fit_to_document)
self._fit_to_document()
self._termWidget = termwidget
self.completer = MyCompleter()
self.model = QtCore.QStringListModel()
self.completer.setModel(self.model)
self.set_model_data(keyword_list=[])
self.completer.insertText.connect(self.insertCompletion)
def set_model_data(self, keyword_list):
self.model.setStringList(keyword_list)
def insertCompletion(self, completion):
tc = self.textCursor()
extra = (len(completion) - len(self.completer.completionPrefix()))
tc.movePosition(QTextCursor.Left)
tc.movePosition(QTextCursor.EndOfWord)
tc.insertText(completion[-extra:])
self.setTextCursor(tc)
self.completer.popup().hide()
def focusInEvent(self, event):
if self.completer:
self.completer.setWidget(self)
QTextEdit.focusInEvent(self, event)
def keyPressEvent(self, event):
"""
Catch keyboard events. Process Enter, Up, Down
"""
if event.matches(QKeySequence.InsertParagraphSeparator):
text = self.toPlainText()
if self._termWidget.is_command_complete(text):
self._termWidget.exec_current_command()
return
elif event.matches(QKeySequence.MoveToNextLine):
text = self.toPlainText()
cursor_pos = self.textCursor().position()
textBeforeEnd = text[cursor_pos:]
if len(textBeforeEnd.split('\n')) <= 1:
self.historyNext.emit()
return
elif event.matches(QKeySequence.MoveToPreviousLine):
text = self.toPlainText()
cursor_pos = self.textCursor().position()
text_before_start = text[:cursor_pos]
# lineCount = len(textBeforeStart.splitlines())
line_count = len(text_before_start.split('\n'))
if len(text_before_start) > 0 and \
(text_before_start[-1] == '\n' or text_before_start[-1] == '\r'):
line_count += 1
if line_count <= 1:
self.historyPrev.emit()
return
elif event.matches(QKeySequence.MoveToNextPage) or \
event.matches(QKeySequence.MoveToPreviousPage):
return self._termWidget.browser().keyPressEvent(event)
tc = self.textCursor()
if event.key() == Qt.Key_Tab and self.completer.popup().isVisible():
self.completer.insertText.emit(self.completer.getSelected())
self.completer.setCompletionMode(QCompleter.PopupCompletion)
return
QTextEdit.keyPressEvent(self, event)
tc.select(QTextCursor.WordUnderCursor)
cr = self.cursorRect()
if len(tc.selectedText()) > 0:
self.completer.setCompletionPrefix(tc.selectedText())
popup = self.completer.popup()
popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
cr.setWidth(self.completer.popup().sizeHintForColumn(0)
+ self.completer.popup().verticalScrollBar().sizeHint().width())
self.completer.complete(cr)
else:
self.completer.popup().hide()
def sizeHint(self):
"""
QWidget sizeHint impelemtation
"""
hint = QTextEdit.sizeHint(self)
hint.setHeight(self._fittedHeight)
return hint
def _fit_to_document(self):
"""
Update widget height to fit all text
"""
documentsize = self.document().size().toSize()
self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height())
self.setMaximumHeight(self._fittedHeight)
self.updateGeometry()
def insertFromMimeData(self, mime_data):
# Paste only plain text.
self.insertPlainText(mime_data.text())
class MyCompleter(QCompleter):
insertText = pyqtSignal(str)
def __init__(self, parent=None):
QCompleter.__init__(self)
self.setCompletionMode(QCompleter.PopupCompletion)
self.highlighted.connect(self.setHighlighted)
def setHighlighted(self, text):
self.lastSelected = text
def getSelected(self):
return self.lastSelected

View File

@ -12,7 +12,7 @@ import inspect # TODO: Remove
import FlatCAMApp
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt
import webbrowser
# import webbrowser
class KeySensitiveListView(QtWidgets.QTreeView):
@ -36,7 +36,7 @@ class KeySensitiveListView(QtWidgets.QTreeView):
keyPressed = QtCore.pyqtSignal(int)
def keyPressEvent(self, event):
super(KeySensitiveListView, self).keyPressEvent(event)
# super(KeySensitiveListView, self).keyPressEvent(event)
self.keyPressed.emit(event.key())
def dragEnterEvent(self, event):
@ -228,6 +228,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
# tasks know that they have to wait until available.
self.promises = set()
self.app = app
### View
self.view = KeySensitiveListView(app)
self.view.setModel(self)
@ -247,7 +249,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
## GUI Events
self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
self.view.activated.connect(self.on_item_activated)
self.view.keyPressed.connect(self.on_key)
# self.view.keyPressed.connect(self.on_key)
self.view.keyPressed.connect(self.app.ui.keyPressEvent)
self.view.clicked.connect(self.on_mouse_down)
self.view.customContextMenuRequested.connect(self.on_menu_request)
@ -260,238 +263,238 @@ class ObjectCollection(QtCore.QAbstractItemModel):
def has_promises(self):
return len(self.promises) > 0
def on_key(self, key):
modifiers = QtWidgets.QApplication.keyboardModifiers()
active = self.get_active()
selected = self.get_selected()
if modifiers == QtCore.Qt.ControlModifier:
if key == QtCore.Qt.Key_A:
self.app.on_selectall()
if key == QtCore.Qt.Key_C:
self.app.on_copy_object()
if key == QtCore.Qt.Key_E:
self.app.on_fileopenexcellon()
if key == QtCore.Qt.Key_G:
self.app.on_fileopengerber()
if key == QtCore.Qt.Key_N:
self.app.on_file_new_click()
if key == QtCore.Qt.Key_M:
self.app.measurement_tool.run()
if key == QtCore.Qt.Key_O:
self.app.on_file_openproject()
if key == QtCore.Qt.Key_S:
self.app.on_file_saveproject()
# Toggle Plot Area
if key == QtCore.Qt.Key_F10:
self.app.on_toggle_plotarea()
return
elif modifiers == QtCore.Qt.ShiftModifier:
# Copy Object Name
# Copy Object Name
if key == QtCore.Qt.Key_C:
self.app.on_copy_name()
# Toggle axis
if key == QtCore.Qt.Key_G:
if self.toggle_axis is False:
self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
self.app.plotcanvas.redraw()
self.app.toggle_axis = True
else:
self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
self.appplotcanvas.redraw()
self.app.toggle_axis = False
# Open Preferences Window
if key == QtCore.Qt.Key_P:
self.app.on_preferences()
return
# Rotate Object by 90 degree CCW
if key == QtCore.Qt.Key_R:
self.app.on_rotate(silent=True, preset=-90)
return
# Run a Script
if key == QtCore.Qt.Key_S:
self.app.on_filerunscript()
return
# Toggle Workspace
if key == QtCore.Qt.Key_W:
self.app.on_workspace_menu()
return
# Skew on X axis
if key == QtCore.Qt.Key_X:
self.app.on_skewx()
return
# Skew on Y axis
if key == QtCore.Qt.Key_Y:
self.app.on_skewy()
return
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
if key == QtCore.Qt.Key_D:
self.app.dblsidedtool.run()
return
# Non-Copper Clear Tool
if key == QtCore.Qt.Key_N:
self.app.ncclear_tool.run()
return
# Transformation Tool
if key == QtCore.Qt.Key_R:
self.app.transform_tool.run()
return
# Cutout Tool
if key == QtCore.Qt.Key_U:
self.app.cutout_tool.run()
return
else:
# Open Manual
if key == QtCore.Qt.Key_F1:
webbrowser.open(self.app.manual_url)
# Open Video Help
if key == QtCore.Qt.Key_F2:
webbrowser.open(self.app.video_url)
# Switch to Project Tab
if key == QtCore.Qt.Key_1:
self.app.on_select_tab('project')
# Switch to Selected Tab
if key == QtCore.Qt.Key_2:
self.app.on_select_tab('selected')
# Switch to Tool Tab
if key == QtCore.Qt.Key_3:
self.app.on_select_tab('tool')
# Delete
if key == QtCore.Qt.Key_Delete and active:
# Delete via the application to
# ensure cleanup of the GUI
active.app.on_delete()
# Space = Toggle Active/Inactive
if key == QtCore.Qt.Key_Space:
for select in selected:
select.ui.plot_cb.toggle()
self.app.delete_selection_shape()
# Copy Object Name
if key == QtCore.Qt.Key_E:
self.app.object2editor()
# Grid toggle
if key == QtCore.Qt.Key_G:
self.app.geo_editor.grid_snap_btn.trigger()
# Jump to coords
if key == QtCore.Qt.Key_J:
self.app.on_jump_to()
# New Excellon
if key == QtCore.Qt.Key_L:
self.app.new_excellon_object()
# Move tool toggle
if key == QtCore.Qt.Key_M:
self.app.move_tool.toggle()
# New Geometry
if key == QtCore.Qt.Key_N:
self.app.on_new_geometry()
# Set Origin
if key == QtCore.Qt.Key_O:
self.app.on_set_origin()
return
# Set Origin
if key == QtCore.Qt.Key_P:
self.app.properties_tool.run()
return
# Change Units
if key == QtCore.Qt.Key_Q:
if self.app.options["units"] == 'MM':
self.app.general_options_form.general_app_group.units_radio.set_value("IN")
else:
self.app.general_options_form.general_app_group.units_radio.set_value("MM")
self.app.on_toggle_units()
# Rotate Object by 90 degree CW
if key == QtCore.Qt.Key_R:
self.app.on_rotate(silent=True, preset=90)
# Shell toggle
if key == QtCore.Qt.Key_S:
self.app.on_toggle_shell()
# Transform Tool
if key == QtCore.Qt.Key_T:
self.app.transform_tool.run()
# Zoom Fit
if key == QtCore.Qt.Key_V:
self.app.on_zoom_fit(None)
# Mirror on X the selected object(s)
if key == QtCore.Qt.Key_X:
self.app.on_flipx()
# Mirror on Y the selected object(s)
if key == QtCore.Qt.Key_Y:
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
if key == QtCore.Qt.Key_Ampersand:
self.app.on_shortcut_list()
if key == QtCore.Qt.Key_QuoteLeft:
self.app.on_shortcut_list()
return
# def on_key(self, key):
# modifiers = QtWidgets.QApplication.keyboardModifiers()
# active = self.get_active()
# selected = self.get_selected()
#
# if modifiers == QtCore.Qt.ControlModifier:
# if key == QtCore.Qt.Key_A:
# self.app.on_selectall()
#
# if key == QtCore.Qt.Key_C:
# self.app.on_copy_object()
#
# if key == QtCore.Qt.Key_E:
# self.app.on_fileopenexcellon()
#
# if key == QtCore.Qt.Key_G:
# self.app.on_fileopengerber()
#
# if key == QtCore.Qt.Key_N:
# self.app.on_file_new_click()
#
# if key == QtCore.Qt.Key_M:
# self.app.measurement_tool.run()
# if key == QtCore.Qt.Key_O:
# self.app.on_file_openproject()
#
# if key == QtCore.Qt.Key_S:
# self.app.on_file_saveproject()
#
# # Toggle Plot Area
# if key == QtCore.Qt.Key_F10:
# self.app.on_toggle_plotarea()
#
# return
# elif modifiers == QtCore.Qt.ShiftModifier:
#
# # Copy Object Name
# # Copy Object Name
# if key == QtCore.Qt.Key_C:
# self.app.on_copy_name()
#
# # Toggle axis
# if key == QtCore.Qt.Key_G:
# if self.toggle_axis is False:
# self.app.plotcanvas.v_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
# self.app.plotcanvas.h_line.set_data(color=(0.70, 0.3, 0.3, 1.0))
# self.app.plotcanvas.redraw()
# self.app.toggle_axis = True
# else:
# self.app.plotcanvas.v_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
#
# self.app.plotcanvas.h_line.set_data(color=(0.0, 0.0, 0.0, 0.0))
# self.appplotcanvas.redraw()
# self.app.toggle_axis = False
#
# # Open Preferences Window
# if key == QtCore.Qt.Key_P:
# self.app.on_preferences()
# return
#
# # Rotate Object by 90 degree CCW
# if key == QtCore.Qt.Key_R:
# self.app.on_rotate(silent=True, preset=-90)
# return
#
# # Run a Script
# if key == QtCore.Qt.Key_S:
# self.app.on_filerunscript()
# return
#
# # Toggle Workspace
# if key == QtCore.Qt.Key_W:
# self.app.on_workspace_menu()
# return
#
# # Skew on X axis
# if key == QtCore.Qt.Key_X:
# self.app.on_skewx()
# return
#
# # Skew on Y axis
# if key == QtCore.Qt.Key_Y:
# self.app.on_skewy()
# return
#
# 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
# if key == QtCore.Qt.Key_D:
# self.app.dblsidedtool.run()
# return
#
# # Non-Copper Clear Tool
# if key == QtCore.Qt.Key_N:
# self.app.ncclear_tool.run()
# return
#
# # Transformation Tool
# if key == QtCore.Qt.Key_R:
# self.app.transform_tool.run()
# return
#
# # Cutout Tool
# if key == QtCore.Qt.Key_U:
# self.app.cutout_tool.run()
# return
#
# else:
# # Open Manual
# if key == QtCore.Qt.Key_F1:
# webbrowser.open(self.app.manual_url)
#
# # Open Video Help
# if key == QtCore.Qt.Key_F2:
# webbrowser.open(self.app.video_url)
#
# # Switch to Project Tab
# if key == QtCore.Qt.Key_1:
# self.app.on_select_tab('project')
#
# # Switch to Selected Tab
# if key == QtCore.Qt.Key_2:
# self.app.on_select_tab('selected')
#
# # Switch to Tool Tab
# if key == QtCore.Qt.Key_3:
# self.app.on_select_tab('tool')
#
# # Delete
# if key == QtCore.Qt.Key_Delete and active:
# # Delete via the application to
# # ensure cleanup of the GUI
# active.app.on_delete()
#
# # Space = Toggle Active/Inactive
# if key == QtCore.Qt.Key_Space:
# for select in selected:
# select.ui.plot_cb.toggle()
# self.app.delete_selection_shape()
#
# # Copy Object Name
# if key == QtCore.Qt.Key_E:
# self.app.object2editor()
#
# # Grid toggle
# if key == QtCore.Qt.Key_G:
# self.app.ui.grid_snap_btn.trigger()
#
# # Jump to coords
# if key == QtCore.Qt.Key_J:
# self.app.on_jump_to()
#
# # New Excellon
# if key == QtCore.Qt.Key_L:
# self.app.new_excellon_object()
#
# # Move tool toggle
# if key == QtCore.Qt.Key_M:
# self.app.move_tool.toggle()
#
# # New Geometry
# if key == QtCore.Qt.Key_N:
# self.app.on_new_geometry()
#
# # Set Origin
# if key == QtCore.Qt.Key_O:
# self.app.on_set_origin()
# return
#
# # Set Origin
# if key == QtCore.Qt.Key_P:
# self.app.properties_tool.run()
# return
#
# # Change Units
# if key == QtCore.Qt.Key_Q:
# if self.app.options["units"] == 'MM':
# self.app.general_options_form.general_app_group.units_radio.set_value("IN")
# else:
# self.app.general_options_form.general_app_group.units_radio.set_value("MM")
# self.app.on_toggle_units()
#
# # Rotate Object by 90 degree CW
# if key == QtCore.Qt.Key_R:
# self.app.on_rotate(silent=True, preset=90)
#
# # Shell toggle
# if key == QtCore.Qt.Key_S:
# self.app.on_toggle_shell()
#
# # Transform Tool
# if key == QtCore.Qt.Key_T:
# self.app.transform_tool.run()
#
# # Zoom Fit
# if key == QtCore.Qt.Key_V:
# self.app.on_zoom_fit(None)
#
# # Mirror on X the selected object(s)
# if key == QtCore.Qt.Key_X:
# self.app.on_flipx()
#
# # Mirror on Y the selected object(s)
# if key == QtCore.Qt.Key_Y:
# 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
# if key == QtCore.Qt.Key_Ampersand:
# self.app.on_shortcut_list()
#
# if key == QtCore.Qt.Key_QuoteLeft:
# self.app.on_shortcut_list()
# return
def on_mouse_down(self, event):
FlatCAMApp.App.log.debug("Mouse button pressed on list")
@ -681,7 +684,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
:rtype: list
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
# FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
return [x.options['name'] for x in self.get_list()]
def get_bounds(self):
@ -875,8 +878,8 @@ class ObjectCollection(QtCore.QAbstractItemModel):
self.set_inactive(name)
def on_list_selection_change(self, current, previous):
FlatCAMApp.App.log.debug("on_list_selection_change()")
FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
# FlatCAMApp.App.log.debug("on_list_selection_change()")
# FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
try:
obj = current.indexes()[0].internalPointer().obj

View File

@ -611,7 +611,8 @@ class ExcellonObjectUI(ObjectUI):
self.tools_box.addLayout(gcode_box)
# temporary action until I finish the feature
self.excellon_gcode_type_radio.setEnabled(False)
self.excellon_gcode_type_radio.setVisible(False)
gcode_type_label.hide()
self.generate_cnc_button = QtWidgets.QPushButton('Create GCode')
self.generate_cnc_button.setToolTip(
@ -783,7 +784,8 @@ class GeometryObjectUI(ObjectUI):
"cut and negative for 'inside' cut."
)
self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
self.tool_offset_entry = FCEntry()
self.tool_offset_entry = FloatEntry()
self.tool_offset_entry.setValidator(QtGui.QDoubleValidator(-9999.9999, 9999.9999, 4))
spacer_lbl = QtWidgets.QLabel(" ")
spacer_lbl.setFixedWidth(80)
@ -1129,9 +1131,14 @@ class CNCObjectUI(ObjectUI):
{"label": "Cut", "value": "cut"}
], stretch=False)
f_lay = QtWidgets.QFormLayout()
f_lay = QtWidgets.QGridLayout()
f_lay.setColumnStretch(1, 1)
f_lay.setColumnStretch(2, 1)
self.custom_box.addLayout(f_lay)
f_lay.addRow(self.cncplot_method_label, self.cncplot_method_combo)
f_lay.addWidget(self.cncplot_method_label, 0, 0)
f_lay.addWidget(self.cncplot_method_combo, 0, 1)
f_lay.addWidget(QtWidgets.QLabel(''), 0, 2)
e1_lbl = QtWidgets.QLabel('')
self.custom_box.addWidget(e1_lbl)

View File

@ -9,9 +9,64 @@ CAD program, and create G-Code for Isolation routing.
=================================================
9.02.2019
- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory
- added into Preferences the Calculator Tools
- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before)
- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor
- changed the messages from status bar on new object creation/selection
- in Geometry Editor fixed the handler for the Rotate shortcut key ('R')
8.02.2019
- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible
- changed the menu entry Toggle Grid name to Toggle Grid Snap
- fixed errors in Toggle Axis
- fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View
- moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI
- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
- changed the shortcut key for Transform Tool from 'T' to 'ALT+T'
- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
- added Double Validator for the Offset value so only float numbers can be entered.
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab
- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value.
7.02.2019
- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object
- commented some debug messages
- imported speedups for shapely
- added a disable menu entry in the canvas contextual menu
- small changes in Tools layout
- added some new icons in the help menu and reorganized this menu
- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section
- changed the Shortcut list shortcut key to F3
- moved some graphical classes out of Tool Shell to GUIElements.py where they belong
- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed
- in Move Tool I've added the type of object that was moved in the status bar message
- color coded the status bar bullet to blue for selection
- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs.
6.02.2019
- fixed the units calculators crash FlatCAM when using comma as decimal separator
- done a regression on Tool Tab default text. It somehow delete Tools in certain scenarios so I got rid of it
- fixed bug in multigeometry geometry not having the bounds in self.options and crashing the GCode generation
- fixed bug that crashed whole application in case that the GCode editor is activated on a Tool gcode that is defective.
- fixed bug in Excellon Slots milling: a value of a dict key was a string instead to be an int. A cast to integer solved it.
- fixed the name self-insert in save dialog file for GCode; added protection in case the save path is None
- fixed FlatCAM crash when trying to make drills GCode out of a file that have only slots.
- changed the messages for Units Conversion
- all key shortcuts work across the entire application; moved all the shortcuts definitions in FlatCAMGUI.keyPressEvent()
- renamed the theme to layout because it is really a layout change
- added plot kind for CNC Job in the App Preferences
- combined the geocutout and cutout_any TCL commands - work in progress
- added a new function (and shortcut key Escape) that when triggered it deselects all selected objects and delete the selection box(es)
- fixed bug in Excellon Gcode generation that made the toolchange X,Y always none regardless of the value in Preferences
- fixed the Tcl Command Geocutout to work with Gerber objects too (besides Geometry objects)
5.02.3019

468
camlib.py
View File

@ -31,6 +31,7 @@ from shapely.wkt import loads as sloads
from shapely.wkt import dumps as sdumps
from shapely.geometry.base import BaseGeometry
from shapely.geometry import shape
from shapely import speedups
from collections import Iterable
@ -101,7 +102,7 @@ class Geometry(object):
self.geo_steps_per_circle = geo_steps_per_circle
if geo_steps_per_circle is None:
geo_steps_per_circle = Geometry.defaults["geo_steps_per_circle"]
geo_steps_per_circle = int(Geometry.defaults["geo_steps_per_circle"])
self.geo_steps_per_circle = geo_steps_per_circle
def make_index(self):
@ -494,7 +495,7 @@ class Geometry(object):
#
# return self.flat_geometry, self.flat_geometry_rtree
def isolation_geometry(self, offset, iso_type=2):
def isolation_geometry(self, offset, iso_type=2, corner=None):
"""
Creates contours around geometry at a given
offset distance.
@ -503,6 +504,7 @@ class Geometry(object):
:type offset: float
:param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
:type integer
:param corner: type of corner for the isolation: 0 = round; 1 = square; 2= beveled (line that connects the ends)
:return: The buffered geometry.
:rtype: Shapely.MultiPolygon or Shapely.Polygon
"""
@ -537,7 +539,11 @@ class Geometry(object):
if offset == 0:
geo_iso = self.solid_geometry
else:
geo_iso = self.solid_geometry.buffer(offset, int(self.geo_steps_per_circle / 4))
if corner is None:
geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4))
else:
geo_iso = self.solid_geometry.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner)
# end of replaced block
if iso_type == 2:
@ -790,7 +796,7 @@ class Geometry(object):
# Can only result in a Polygon or MultiPolygon
# NOTE: The resulting polygon can be "empty".
current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle / 4))
current = polygon.buffer((-tooldia / 1.999999), int(int(steps_per_circle) / 4))
if current.area == 0:
# Otherwise, trying to to insert current.exterior == None
# into the FlatCAMStorage will fail.
@ -813,7 +819,7 @@ class Geometry(object):
while True:
# Can only result in a Polygon or MultiPolygon
current = current.buffer(-tooldia * (1 - overlap), int(steps_per_circle / 4))
current = current.buffer(-tooldia * (1 - overlap), int(int(steps_per_circle) / 4))
if current.area > 0:
# current can be a MultiPolygon
@ -829,13 +835,13 @@ class Geometry(object):
for i in current.interiors:
geoms.insert(i)
else:
print("Current Area is zero")
log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero")
break
# Optimization: Reduce lifts
if connect:
# log.debug("Reducing tool lifts...")
geoms = Geometry.paint_connect(geoms, polygon, tooldia, steps_per_circle)
geoms = Geometry.paint_connect(geoms, polygon, tooldia, int(steps_per_circle))
return geoms
@ -1873,11 +1879,11 @@ class Gerber (Geometry):
# How to discretize a circle.
if steps_per_circle is None:
steps_per_circle = Gerber.defaults['steps_per_circle']
self.steps_per_circle = steps_per_circle
steps_per_circle = int(Gerber.defaults['steps_per_circle'])
self.steps_per_circle = int(steps_per_circle)
# Initialize parent
Geometry.__init__(self, geo_steps_per_circle=steps_per_circle)
Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
self.solid_geometry = Polygon()
@ -3268,10 +3274,10 @@ class Excellon(Geometry):
"""
if geo_steps_per_circle is None:
geo_steps_per_circle = Excellon.defaults['geo_steps_per_circle']
self.geo_steps_per_circle = geo_steps_per_circle
geo_steps_per_circle = int(Excellon.defaults['geo_steps_per_circle'])
self.geo_steps_per_circle = int(geo_steps_per_circle)
Geometry.__init__(self, geo_steps_per_circle=geo_steps_per_circle)
Geometry.__init__(self, geo_steps_per_circle=int(geo_steps_per_circle))
# dictionary to store tools, see above for description
self.tools = {}
@ -4382,10 +4388,10 @@ class CNCjob(Geometry):
# Used when parsing G-code arcs
if steps_per_circle is None:
steps_per_circle = CNCjob.defaults["steps_per_circle"]
self.steps_per_circle = steps_per_circle
steps_per_circle = int(CNCjob.defaults["steps_per_circle"])
self.steps_per_circle = int(steps_per_circle)
Geometry.__init__(self, geo_steps_per_circle=steps_per_circle)
Geometry.__init__(self, geo_steps_per_circle=int(steps_per_circle))
self.kind = kind
self.units = units
@ -4549,7 +4555,7 @@ class CNCjob(Geometry):
elif drillz == 0:
self.app.inform.emit("[WARNING] The Cut Z parameter is zero. "
"There will be no cut, skipping %s file" % exobj.options['name'])
return
return 'fail'
else:
self.z_cut = drillz
@ -4670,139 +4676,188 @@ class CNCjob(Geometry):
if current_platform == '64bit':
if excellon_optimization_type == 'M':
log.debug("Using OR-Tools Metaheuristic Guided Local Search drill path optimization.")
for tool in tools:
self.tool=tool
self.postdata['toolC']=exobj.tools[tool]["C"]
if exobj.drills:
for tool in tools:
self.tool=tool
self.postdata['toolC']=exobj.tools[tool]["C"]
################################################
# Create the data.
node_list = []
locations = create_data_array()
tsp_size = len(locations)
num_routes = 1 # The number of routes, which is 1 in the TSP.
# Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
depot = 0
# Create routing model.
if tsp_size > 0:
routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
################################################
# Create the data.
node_list = []
locations = create_data_array()
tsp_size = len(locations)
num_routes = 1 # The number of routes, which is 1 in the TSP.
# Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
depot = 0
# Create routing model.
if tsp_size > 0:
routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
# Set search time limit in milliseconds.
if float(self.app.defaults["excellon_search_time"]) != 0:
search_parameters.time_limit_ms = int(
float(self.app.defaults["excellon_search_time"]) * 1000)
# Set search time limit in milliseconds.
if float(self.app.defaults["excellon_search_time"]) != 0:
search_parameters.time_limit_ms = int(
float(self.app.defaults["excellon_search_time"]) * 1000)
else:
search_parameters.time_limit_ms = 3000
# Callback to the distance function. The callback takes two
# arguments (the from and to node indices) and returns the distance between them.
dist_between_locations = CreateDistanceCallback()
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
# Solve, returns a solution if any.
assignment = routing.SolveWithParameters(search_parameters)
if assignment:
# Solution cost.
log.info("Total distance: " + str(assignment.ObjectiveValue()))
# Inspect solution.
# Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
route_number = 0
node = routing.Start(route_number)
start_node = node
while not routing.IsEnd(node):
node_list.append(node)
node = assignment.Value(routing.NextVar(node))
else:
log.warning('No solution found.')
else:
search_parameters.time_limit_ms = 3000
log.warning('Specify an instance greater than 0.')
################################################
# Callback to the distance function. The callback takes two
# arguments (the from and to node indices) and returns the distance between them.
dist_between_locations = CreateDistanceCallback()
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
# Only if tool has points.
if tool in points:
# Tool change sequence (optional)
if toolchange:
gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
else:
gcode += self.doformat(p.spindle_code)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
# Solve, returns a solution if any.
assignment = routing.SolveWithParameters(search_parameters)
# Drillling!
for k in node_list:
locx = locations[k][0]
locy = locations[k][1]
if assignment:
# Solution cost.
log.info("Total distance: " + str(assignment.ObjectiveValue()))
gcode += self.doformat(p.rapid_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.lift_code, x=locx, y=locy)
measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
self.oldx = locx
self.oldy = locy
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...')
return 'fail'
# Inspect solution.
# Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
route_number = 0
node = routing.Start(route_number)
start_node = node
while not routing.IsEnd(node):
node_list.append(node)
node = assignment.Value(routing.NextVar(node))
else:
log.warning('No solution found.')
else:
log.warning('Specify an instance greater than 0.')
################################################
# Only if tool has points.
if tool in points:
# Tool change sequence (optional)
if toolchange:
gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
else:
gcode += self.doformat(p.spindle_code)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
# Drillling!
for k in node_list:
locx = locations[k][0]
locy = locations[k][1]
gcode += self.doformat(p.rapid_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.lift_code, x=locx, y=locy)
measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
self.oldx = locx
self.oldy = locy
log.debug("The total travel distance with OR-TOOLS Metaheuristics is: %s" % str(measured_distance))
elif excellon_optimization_type == 'B':
log.debug("Using OR-Tools Basic drill path optimization.")
for tool in tools:
self.tool=tool
self.postdata['toolC']=exobj.tools[tool]["C"]
if exobj.drills:
for tool in tools:
self.tool=tool
self.postdata['toolC']=exobj.tools[tool]["C"]
################################################
node_list = []
locations = create_data_array()
tsp_size = len(locations)
num_routes = 1 # The number of routes, which is 1 in the TSP.
################################################
node_list = []
locations = create_data_array()
tsp_size = len(locations)
num_routes = 1 # The number of routes, which is 1 in the TSP.
# Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
depot = 0
# Nodes are indexed from 0 to tsp_size - 1. The depot is the starting node of the route.
depot = 0
# Create routing model.
if tsp_size > 0:
routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
# Create routing model.
if tsp_size > 0:
routing = pywrapcp.RoutingModel(tsp_size, num_routes, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
# Callback to the distance function. The callback takes two
# arguments (the from and to node indices) and returns the distance between them.
dist_between_locations = CreateDistanceCallback()
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
# Callback to the distance function. The callback takes two
# arguments (the from and to node indices) and returns the distance between them.
dist_between_locations = CreateDistanceCallback()
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
# Solve, returns a solution if any.
assignment = routing.SolveWithParameters(search_parameters)
# Solve, returns a solution if any.
assignment = routing.SolveWithParameters(search_parameters)
if assignment:
# Solution cost.
log.info("Total distance: " + str(assignment.ObjectiveValue()))
if assignment:
# Solution cost.
log.info("Total distance: " + str(assignment.ObjectiveValue()))
# Inspect solution.
# Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
route_number = 0
node = routing.Start(route_number)
start_node = node
# Inspect solution.
# Only one route here; otherwise iterate from 0 to routing.vehicles() - 1.
route_number = 0
node = routing.Start(route_number)
start_node = node
while not routing.IsEnd(node):
node_list.append(node)
node = assignment.Value(routing.NextVar(node))
while not routing.IsEnd(node):
node_list.append(node)
node = assignment.Value(routing.NextVar(node))
else:
log.warning('No solution found.')
else:
log.warning('No solution found.')
else:
log.warning('Specify an instance greater than 0.')
################################################
log.warning('Specify an instance greater than 0.')
################################################
# Only if tool has points.
if tool in points:
# Tool change sequence (optional)
if toolchange:
gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
else:
gcode += self.doformat(p.spindle_code)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
# Drillling!
for k in node_list:
locx = locations[k][0]
locy = locations[k][1]
gcode += self.doformat(p.rapid_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.lift_code, x=locx, y=locy)
measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
self.oldx = locx
self.oldy = locy
else:
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...')
return 'fail'
log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
else:
self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.")
return 'fail'
else:
log.debug("Using Travelling Salesman drill path optimization.")
for tool in tools:
if exobj.drills:
self.tool = tool
self.postdata['toolC'] = exobj.tools[tool]["C"]
# Only if tool has points.
if tool in points:
# Tool change sequence (optional)
if toolchange:
gcode += self.doformat(p.toolchange_code,toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
@ -4812,52 +4867,23 @@ class CNCjob(Geometry):
gcode += self.doformat(p.dwell_code) # Dwell time
# Drillling!
for k in node_list:
locx = locations[k][0]
locy = locations[k][1]
gcode += self.doformat(p.rapid_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.lift_code, x=locx, y=locy)
measured_distance += abs(distance_euclidian(locx, locy, self.oldx, self.oldy))
self.oldx = locx
self.oldy = locy
log.debug("The total travel distance with OR-TOOLS Basic Algorithm is: %s" % str(measured_distance))
else:
self.app.inform.emit("[ERROR_NOTCL] Wrong optimization type selected.")
return
else:
log.debug("Using Travelling Salesman drill path optimization.")
for tool in tools:
self.tool = tool
self.postdata['toolC'] = exobj.tools[tool]["C"]
altPoints = []
for point in points[tool]:
altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0]))
# Only if tool has points.
if tool in points:
# Tool change sequence (optional)
if toolchange:
gcode += self.doformat(p.toolchange_code, toolchangexy=(self.oldx, self.oldy))
gcode += self.doformat(p.spindle_code) # Spindle start)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
for point in self.optimized_travelling_salesman(altPoints):
gcode += self.doformat(p.rapid_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.lift_code, x=point[0], y=point[1])
measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy))
self.oldx = point[0]
self.oldy = point[1]
else:
gcode += self.doformat(p.spindle_code)
if self.dwell is True:
gcode += self.doformat(p.dwell_code) # Dwell time
# Drillling!
altPoints = []
for point in points[tool]:
altPoints.append((point.coords.xy[0][0], point.coords.xy[1][0]))
for point in self.optimized_travelling_salesman(altPoints):
gcode += self.doformat(p.rapid_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.lift_code, x=point[0], y=point[1])
measured_distance += abs(distance_euclidian(point[0], point[1], self.oldx, self.oldy))
self.oldx = point[0]
self.oldy = point[1]
log.debug("camlib.CNCJob.generate_from_excellon_by_tool() --> "
"The loaded Excellon file has no drills ...")
self.app.inform.emit('[ERROR_NOTCL]The loaded Excellon file has no drills ...')
return 'fail'
log.debug("The total travel distance with Travelling Salesman Algorithm is: %s" % str(measured_distance))
gcode += self.doformat(p.spindle_stop_code) # Spindle stop
@ -4867,6 +4893,7 @@ class CNCjob(Geometry):
log.debug("The total travel distance including travel to end position is: %s" %
str(measured_distance) + '\n')
self.gcode = gcode
return 'OK'
def generate_from_multitool_geometry(self, geometry, append=True,
tooldia=None, offset=0.0, tolerance=0, z_cut=1.0, z_move=2.0,
@ -4912,25 +4939,25 @@ class CNCjob(Geometry):
flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
log.debug("%d paths" % len(flat_geometry))
self.tooldia = tooldia
self.z_cut = z_cut
self.z_move = z_move
self.tooldia = float(tooldia) if tooldia else None
self.z_cut = float(z_cut) if z_cut else None
self.z_move = float(z_move) if z_move else None
self.feedrate = feedrate
self.feedrate_z = feedrate_z
self.feedrate_rapid = feedrate_rapid
self.feedrate = float(feedrate) if feedrate else None
self.feedrate_z = float(feedrate_z) if feedrate_z else None
self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
self.spindlespeed = spindlespeed
self.spindlespeed = int(spindlespeed) if spindlespeed else None
self.dwell = dwell
self.dwelltime = dwelltime
self.dwelltime = float(dwelltime) if dwelltime else None
self.startz = startz
self.endz = endz
self.startz = float(startz) if startz else None
self.endz = float(endz) if endz else None
self.depthpercut = depthpercut
self.depthpercut = float(depthpercut) if depthpercut else None
self.multidepth = multidepth
self.toolchangez = toolchangez
self.toolchangez = float(toolchangez) if toolchangez else None
try:
if toolchangexy == '':
@ -5093,14 +5120,57 @@ class CNCjob(Geometry):
"from a Geometry object without solid_geometry.")
temp_solid_geometry = []
def bounds_rec(obj):
if type(obj) is list:
minx = Inf
miny = Inf
maxx = -Inf
maxy = -Inf
for k in obj:
if type(k) is dict:
for key in k:
minx_, miny_, maxx_, maxy_ = bounds_rec(k[key])
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
else:
minx_, miny_, maxx_, maxy_ = bounds_rec(k)
minx = min(minx, minx_)
miny = min(miny, miny_)
maxx = max(maxx, maxx_)
maxy = max(maxy, maxy_)
return minx, miny, maxx, maxy
else:
# it's a Shapely object, return it's bounds
return obj.bounds
if offset != 0.0:
offset_for_use = offset
if offset <0:
a, b, c, d = bounds_rec(geometry.solid_geometry)
# if the offset is less than half of the total length or less than half of the total width of the
# solid geometry it's obvious we can't do the offset
if -offset > ((c - a) / 2) or -offset > ((d - b) / 2):
self.app.inform.emit("[ERROR_NOTCL]The Tool Offset value is too negative to use "
"for the current_geometry.\n"
"Raise the value (in module) and try again.")
return 'fail'
# hack: make offset smaller by 0.0000000001 which is insignificant difference but allow the job
# to continue
elif -offset == ((c - a) / 2) or -offset == ((d - b) / 2):
offset_for_use = offset - 0.0000000001
for it in geometry.solid_geometry:
# if the geometry is a closed shape then create a Polygon out of it
if isinstance(it, LineString):
c = it.coords
if c[0] == c[-1]:
it = Polygon(it)
temp_solid_geometry.append(it.buffer(offset, join_style=2))
temp_solid_geometry.append(it.buffer(offset_for_use, join_style=2))
else:
temp_solid_geometry = geometry.solid_geometry
@ -5108,25 +5178,33 @@ class CNCjob(Geometry):
flat_geometry = self.flatten(temp_solid_geometry, pathonly=True)
log.debug("%d paths" % len(flat_geometry))
self.tooldia = tooldia
self.z_cut = z_cut
self.z_move = z_move
self.tooldia = float(tooldia) if tooldia else None
self.feedrate = feedrate
self.feedrate_z = feedrate_z
self.feedrate_rapid = feedrate_rapid
self.z_cut = float(z_cut) if z_cut else None
self.z_move = float(z_move) if z_move else None
self.feedrate = float(feedrate) if feedrate else None
self.feedrate_z = float(feedrate_z) if feedrate_z else None
self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else None
self.spindlespeed = int(spindlespeed) if spindlespeed else None
self.spindlespeed = spindlespeed
self.dwell = dwell
self.dwelltime = dwelltime
self.startz = startz
self.endz = endz
self.dwelltime = float(dwelltime) if dwelltime else None
self.startz = float(startz) if startz else None
self.endz = float(endz) if endz else None
self.depthpercut = float(depthpercut) if depthpercut else None
self.depthpercut = depthpercut
self.multidepth = multidepth
self.toolchangez = toolchangez
self.toolchangez = float(toolchangez) if toolchangez else None
try:
if toolchangexy == '':

View File

@ -21,6 +21,40 @@ class ToolCalculator(FlatCAMTool):
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
self.layout.addWidget(title_label)
######################
## Units Calculator ##
######################
self.unists_spacer_label = QtWidgets.QLabel(" ")
self.layout.addWidget(self.unists_spacer_label)
## Title of the Units Calculator
units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName)
self.layout.addWidget(units_label)
#Grid Layout
grid_units_layout = QtWidgets.QGridLayout()
self.layout.addLayout(grid_units_layout)
inch_label = QtWidgets.QLabel("INCH")
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.setFixedWidth(70)
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.mm_entry = FCEntry()
# self.mm_entry.setFixedWidth(130)
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")
grid_units_layout.addWidget(self.mm_entry, 1, 0)
grid_units_layout.addWidget(self.inch_entry, 1, 1)
############################
## V-shape Tool Calculator ##
############################
@ -83,38 +117,6 @@ class ToolCalculator(FlatCAMTool):
form_layout.addRow(self.empty_label, self.calculate_vshape_button)
######################
## Units Calculator ##
######################
self.unists_spacer_label = QtWidgets.QLabel(" ")
self.layout.addWidget(self.unists_spacer_label)
## Title of the Units Calculator
units_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.unitsName)
self.layout.addWidget(units_label)
#Grid Layout
grid_units_layout = QtWidgets.QGridLayout()
self.layout.addLayout(grid_units_layout)
inch_label = QtWidgets.QLabel("INCH")
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.setFixedWidth(70)
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.mm_entry = FCEntry()
# self.mm_entry.setFixedWidth(130)
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")
grid_units_layout.addWidget(self.mm_entry, 1, 0)
grid_units_layout.addWidget(self.inch_entry, 1, 1)
####################################
## ElectroPlating Tool Calculator ##
@ -224,27 +226,31 @@ class ToolCalculator(FlatCAMTool):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
def set_tool_ui(self):
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
## 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)
length = self.app.defaults["tools_calc_electro_length"]
width = self.app.defaults["tools_calc_electro_width"]
density = self.app.defaults["tools_calc_electro_cdensity"]
growth = self.app.defaults["tools_calc_electro_growth"]
self.pcblength_entry.set_value(length)
self.pcbwidth_entry.set_value(width)
self.cdensity_entry.set_value(density)
self.growth_entry.set_value(growth)
self.cvalue_entry.set_value(0.00)
self.time_entry.set_value(0.0)
if self.app.defaults["units"] == 'MM':
self.tipDia_entry.set_value('0.2')
self.tipAngle_entry.set_value('45')
self.cutDepth_entry.set_value('0.25')
self.effectiveToolDia_entry.set_value('0.39')
else:
self.tipDia_entry.set_value('7.87402')
self.tipAngle_entry.set_value('45')
self.cutDepth_entry.set_value('9.84252')
self.effectiveToolDia_entry.set_value('15.35433')
tip_dia = self.app.defaults["tools_calc_vshape_tip_dia"]
tip_angle = self.app.defaults["tools_calc_vshape_tip_angle"]
cut_z = self.app.defaults["tools_calc_vshape_cut_z"]
self.tipDia_entry.set_value(tip_dia)
self.tipAngle_entry.set_value(tip_angle)
self.cutDepth_entry.set_value(cut_z)
self.effectiveToolDia_entry.set_value('0.0000')
def on_calculate_tool_dia(self):
# Calculation:

View File

@ -184,25 +184,22 @@ class Measurement(FlatCAMTool):
# disconnect the mouse/key events from functions of measurement tool
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move_meas)
self.app.plotcanvas.vis_disconnect('mouse_press', self.on_click_meas)
self.app.plotcanvas.vis_disconnect('key_release', self.on_key_release_meas)
# reconnect the mouse/key events to the functions from where the tool was called
if self.app.call_source == 'app':
self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('key_press', self.app.on_key_over_plot)
self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor':
self.app.geo_editor.canvas.vis_connect('mouse_move', self.app.geo_editor.on_canvas_move)
self.app.geo_editor.canvas.vis_connect('mouse_press', self.app.geo_editor.on_canvas_click)
self.app.geo_editor.canvas.vis_connect('key_press', self.app.geo_editor.on_canvas_key)
self.app.geo_editor.canvas.vis_connect('key_release', self.app.geo_editor.on_canvas_key_release)
self.app.geo_editor.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor':
self.app.exc_editor.canvas.vis_connect('mouse_move', self.app.exc_editor.on_canvas_move)
self.app.exc_editor.canvas.vis_connect('mouse_press', self.app.exc_editor.on_canvas_click)
self.app.exc_editor.canvas.vis_connect('key_press', self.app.exc_editor.on_canvas_key)
self.app.exc_editor.canvas.vis_connect('key_release', self.app.exc_editor.on_canvas_key_release)
self.app.exc_editor.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
self.clicked_meas = 0
@ -219,19 +216,17 @@ class Measurement(FlatCAMTool):
if self.app.call_source == 'app':
self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('key_press', self.app.on_key_over_plot)
self.app.plotcanvas.vis_disconnect('key_press', self.app.ui.keyPressEvent)
self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor':
self.app.geo_editor.canvas.vis_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
self.app.geo_editor.canvas.vis_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
self.app.geo_editor.canvas.vis_disconnect('key_press', self.app.geo_editor.on_canvas_key)
self.app.geo_editor.canvas.vis_disconnect('key_release', self.app.geo_editor.on_canvas_key_release)
self.app.geo_editor.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor':
self.app.exc_editor.canvas.vis_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
self.app.exc_editor.canvas.vis_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
self.app.exc_editor.canvas.vis_disconnect('key_press', self.app.exc_editor.on_canvas_key)
self.app.exc_editor.canvas.vis_disconnect('key_release', self.app.exc_editor.on_canvas_key_release)
self.app.exc_editor.canvas.vis_disconnect('mouse_release', self.app.exc_editor.on_canvas_click_release)
# we can safely connect the app mouse events to the measurement tool

View File

@ -47,7 +47,7 @@ class ToolMove(FlatCAMTool):
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.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
self.clicked_move = 0
@ -139,12 +139,13 @@ class ToolMove(FlatCAMTool):
proc.done()
# delete the selection bounding box
self.delete_shape()
self.app.inform.emit('[success]%s object was moved ...' %
str(sel_obj.kind).capitalize())
self.app.worker_task.emit({'fcn': job_move, 'params': [self]})
self.clicked_move = 0
self.toggle()
self.app.inform.emit("[success]Object was moved ...")
return
except TypeError:

View File

@ -5,6 +5,7 @@ from copy import copy,deepcopy
from ObjectCollection import *
import time
class NonCopperClear(FlatCAMTool, Gerber):
toolName = "Non-Copper Clearing"
@ -446,6 +447,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter to add, in Float format.")
return
if tool_dia == 0:
self.app.inform.emit("[WARNING_NOTCL] Please enter a tool diameter with non-zero value, in Float format.")
return
# construct a list of all 'tooluid' in the self.tools
tool_uid_list = []
for tooluid_key in self.ncc_tools:
@ -618,7 +623,6 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.inform.emit("[ERROR_NOTCL]Could not retrieve object: %s" % self.obj_name)
return "Could not retrieve object: %s" % self.obj_name
# Prepare non-copper polygons
try:
bounding_box = self.ncc_obj.solid_geometry.envelope.buffer(distance=margin, join_style=JOIN_STYLE.mitre)
@ -626,7 +630,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
self.app.inform.emit("[ERROR_NOTCL]No Gerber file available.")
return
# calculate the empty area by substracting the solid_geometry from the object bounding box geometry
# calculate the empty area by subtracting the solid_geometry from the object bounding box geometry
empty = self.ncc_obj.get_empty_area(bounding_box)
if type(empty) is Polygon:
empty = MultiPolygon([empty])

View File

@ -750,7 +750,9 @@ class ToolPaint(FlatCAMTool, Gerber):
overlap=overlap,
connect=connect,
contour=contour)
self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
self.app.plotcanvas.vis_connect('mouse_press', doit)
def paint_poly(self, obj, inside_pt, tooldia, overlap,
@ -839,6 +841,17 @@ class ToolPaint(FlatCAMTool, Gerber):
return None
geo_obj.solid_geometry = []
try:
a, b, c, d = poly.bounds()
geo_obj.options['xmin'] = a
geo_obj.options['ymin'] = b
geo_obj.options['xmax'] = c
geo_obj.options['ymax'] = d
except Exception as e:
log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
return
try:
poly_buf = poly.buffer(-paint_margin)
if isinstance(poly_buf, MultiPolygon):
@ -988,6 +1001,16 @@ class ToolPaint(FlatCAMTool, Gerber):
sorted_tools.append(float(self.tools_table.item(row, 1).text()))
sorted_tools.sort(reverse=True)
try:
a, b, c, d = obj.bounds()
geo_obj.options['xmin'] = a
geo_obj.options['ymin'] = b
geo_obj.options['xmax'] = c
geo_obj.options['ymax'] = d
except Exception as e:
log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
return
total_geometry = []
current_uid = int(1)
geo_obj.solid_geometry = []
@ -1085,6 +1108,16 @@ class ToolPaint(FlatCAMTool, Gerber):
current_uid = int(1)
geo_obj.solid_geometry = []
try:
a, b, c, d = obj.bounds()
geo_obj.options['xmin'] = a
geo_obj.options['ymin'] = b
geo_obj.options['xmax'] = c
geo_obj.options['ymax'] = d
except Exception as e:
log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
return
for tool_dia in sorted_tools:
for geo in recurse(obj.solid_geometry):
try:

View File

@ -6,165 +6,12 @@
# MIT Licence #
############################################################
# from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QVBoxLayout, QWidget
from GUIElements import _BrowserTextEdit, _ExpandableTextEdit
import html
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import Qt, QStringListModel
from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor
from PyQt5.QtWidgets import QLineEdit, QSizePolicy, QTextEdit, QVBoxLayout, QWidget, QCompleter, QAction
class _BrowserTextEdit(QTextEdit):
def __init__(self, version):
QTextEdit.__init__(self)
self.menu = None
self.version = version
def contextMenuEvent(self, event):
self.menu = self.createStandardContextMenu(event.pos())
clear_action = QAction("Clear", self)
clear_action.setShortcut(QKeySequence(Qt.Key_Delete)) # it's not working, the shortcut
self.menu.addAction(clear_action)
clear_action.triggered.connect(self.clear)
self.menu.exec_(event.globalPos())
def clear(self):
QTextEdit.clear(self)
text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version
text = html.escape(text)
text = text.replace('\n', '<br/>')
self.moveCursor(QTextCursor.End)
self.insertHtml(text)
class _ExpandableTextEdit(QTextEdit):
"""
Class implements edit line, which expands themselves automatically
"""
historyNext = pyqtSignal()
historyPrev = pyqtSignal()
def __init__(self, termwidget, *args):
QTextEdit.__init__(self, *args)
self.setStyleSheet("font: 9pt \"Courier\";")
self._fittedHeight = 1
self.textChanged.connect(self._fit_to_document)
self._fit_to_document()
self._termWidget = termwidget
self.completer = MyCompleter()
self.model = QStringListModel()
self.completer.setModel(self.model)
self.set_model_data(keyword_list=[])
self.completer.insertText.connect(self.insertCompletion)
def set_model_data(self, keyword_list):
self.model.setStringList(keyword_list)
def insertCompletion(self, completion):
tc = self.textCursor()
extra = (len(completion) - len(self.completer.completionPrefix()))
tc.movePosition(QTextCursor.Left)
tc.movePosition(QTextCursor.EndOfWord)
tc.insertText(completion[-extra:])
self.setTextCursor(tc)
self.completer.popup().hide()
def focusInEvent(self, event):
if self.completer:
self.completer.setWidget(self)
QTextEdit.focusInEvent(self, event)
def keyPressEvent(self, event):
"""
Catch keyboard events. Process Enter, Up, Down
"""
if event.matches(QKeySequence.InsertParagraphSeparator):
text = self.toPlainText()
if self._termWidget.is_command_complete(text):
self._termWidget.exec_current_command()
return
elif event.matches(QKeySequence.MoveToNextLine):
text = self.toPlainText()
cursor_pos = self.textCursor().position()
textBeforeEnd = text[cursor_pos:]
if len(textBeforeEnd.split('\n')) <= 1:
self.historyNext.emit()
return
elif event.matches(QKeySequence.MoveToPreviousLine):
text = self.toPlainText()
cursor_pos = self.textCursor().position()
text_before_start = text[:cursor_pos]
# lineCount = len(textBeforeStart.splitlines())
line_count = len(text_before_start.split('\n'))
if len(text_before_start) > 0 and \
(text_before_start[-1] == '\n' or text_before_start[-1] == '\r'):
line_count += 1
if line_count <= 1:
self.historyPrev.emit()
return
elif event.matches(QKeySequence.MoveToNextPage) or \
event.matches(QKeySequence.MoveToPreviousPage):
return self._termWidget.browser().keyPressEvent(event)
tc = self.textCursor()
if event.key() == Qt.Key_Tab and self.completer.popup().isVisible():
self.completer.insertText.emit(self.completer.getSelected())
self.completer.setCompletionMode(QCompleter.PopupCompletion)
return
QTextEdit.keyPressEvent(self, event)
tc.select(QTextCursor.WordUnderCursor)
cr = self.cursorRect()
if len(tc.selectedText()) > 0:
self.completer.setCompletionPrefix(tc.selectedText())
popup = self.completer.popup()
popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
cr.setWidth(self.completer.popup().sizeHintForColumn(0)
+ self.completer.popup().verticalScrollBar().sizeHint().width())
self.completer.complete(cr)
else:
self.completer.popup().hide()
def sizeHint(self):
"""
QWidget sizeHint impelemtation
"""
hint = QTextEdit.sizeHint(self)
hint.setHeight(self._fittedHeight)
return hint
def _fit_to_document(self):
"""
Update widget height to fit all text
"""
documentsize = self.document().size().toSize()
self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height())
self.setMaximumHeight(self._fittedHeight)
self.updateGeometry()
def insertFromMimeData(self, mime_data):
# Paste only plain text.
self.insertPlainText(mime_data.text())
class MyCompleter(QCompleter):
insertText = pyqtSignal(str)
def __init__(self, parent=None):
QCompleter.__init__(self)
self.setCompletionMode(QCompleter.PopupCompletion)
self.highlighted.connect(self.setHighlighted)
def setHighlighted(self, text):
self.lastSelected = text
def getSelected(self):
return self.lastSelected
class TermWidget(QWidget):
@ -234,7 +81,7 @@ class TermWidget(QWidget):
"""
Convert text to HTML for inserting it to browser
"""
assert style in ('in', 'out', 'err', 'warning', 'success')
assert style in ('in', 'out', 'err', 'warning', 'success', 'selected')
text = html.escape(text)
text = text.replace('\n', '<br/>')
@ -247,6 +94,8 @@ class TermWidget(QWidget):
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
elif style == 'selected':
text = '<span style="font-weight: bold; color: rgb(0, 8, 255);">%s</span>' % text
else:
text = '<span>%s</span>' % text # without span <br/> is ignored!!!
@ -313,6 +162,11 @@ class TermWidget(QWidget):
"""
self._append_to_browser('success', text)
def append_selected(self, text):
"""Appent text to output widget
"""
self._append_to_browser('selected', text)
def append_warning(self, text):
"""Appent text to output widget
"""
@ -352,6 +206,7 @@ class TermWidget(QWidget):
self._edit.setPlainText(self._history[self._historyIndex])
self._edit.moveCursor(QTextCursor.End)
class FCShell(TermWidget):
def __init__(self, sysShell, version, *args):
TermWidget.__init__(self, version, *args)

View File

@ -45,7 +45,7 @@ class default(FlatCAMPostProc):
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'
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'

BIN
share/about32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
share/bluelight12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

BIN
share/letter_t_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

BIN
share/youtube32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

@ -1,179 +0,0 @@
from ObjectCollection import *
from tclCommands.TclCommand import TclCommand
class TclCommandCutoutAny(TclCommand):
"""
Tcl shell command to create a board cutout geometry. Allow cutout for any shape.
example:
"""
# List of all command aliases, to be able use old
# names for backward compatibility (add_poly, add_polygon)
aliases = ['cutout_any', 'cut_any']
# Dictionary of types from Tcl command, needs to be ordered
arg_names = collections.OrderedDict([
('name', str),
])
# Dictionary of types from Tcl command, needs to be ordered,
# this is for options like -optionname value
option_types = collections.OrderedDict([
('dia', float),
('margin', float),
('gapsize', float),
('gaps', str)
])
# array of mandatory options for current Tcl command: required = {'name','outname'}
required = ['name']
# structured help for current command, args needs to be ordered
help = {
'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape',
'args': collections.OrderedDict([
('name', 'Name of the object.'),
('dia', 'Tool diameter.'),
('margin', 'Margin over bounds.'),
('gapsize', 'size of gap.'),
('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, "
"'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts")
]),
'examples': []
}
def execute(self, args, unnamed_args):
"""
:param args:
:param unnamed_args:
:return:
"""
def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts)
if 'name' in args:
name = args['name']
else:
self.app.inform.emit(
"[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
return
if 'margin' in args:
margin = args['margin']
else:
margin = 0.001
if 'dia' in args:
dia = args['dia']
else:
dia = 0.1
if 'gaps' in args:
gaps = args['gaps']
else:
gaps = 4
if 'gapsize' in args:
gapsize = args['gapsize']
else:
gapsize = 0.1
# Get source object.
try:
cutout_obj = self.app.collection.get_by_name(str(name))
except:
return "Could not retrieve object: %s" % name
if 0 in {dia}:
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."
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. "
"Fill in a correct value and retry. ")
return
# Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = cutout_obj.bounds()
px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) + margin
lenghtx = (xmax - xmin) + (margin * 2)
lenghty = (ymax - ymin) + (margin * 2)
gapsize = gapsize + (dia / 2)
if isinstance(cutout_obj, FlatCAMGeometry):
# rename the obj name so it can be identified as cutout
cutout_obj.options["name"] += "_cutout"
elif isinstance(cutout_obj, FlatCAMGerber):
cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp")
ext_obj = self.app.collection.get_by_name("_temp")
def geo_init(geo_obj, app_obj):
geo_obj.solid_geometry = obj_exteriors
outname = cutout_obj.options["name"] + "_cutout"
obj_exteriors = ext_obj.get_exteriors()
self.app.new_object('geometry', outname, geo_init)
self.app.collection.set_all_inactive()
self.app.collection.set_active("_temp")
self.app.on_delete()
cutout_obj = self.app.collection.get_by_name(outname)
else:
self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
return
try:
gaps_u = int(gaps)
except ValueError:
gaps_u = gaps
if gaps_u == 8 or gaps_u == '2lr':
subtract_rectangle(cutout_obj,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if gaps_u == 8 or gaps_u == '2tb':
subtract_rectangle(cutout_obj,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
subtract_rectangle(cutout_obj,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if gaps_u == 4 or gaps_u == 'lr':
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps_u == 4 or gaps_u == 'tb':
subtract_rectangle(cutout_obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
cutout_obj.plot()
self.app.inform.emit("[success]Any-form Cutout operation finished.")

View File

@ -1,23 +1,26 @@
from ObjectCollection import *
from tclCommands.TclCommand import TclCommandSignaled
from copy import deepcopy
class TclCommandGeoCutout(TclCommandSignaled):
"""
Tcl shell command to cut holding gaps from geometry.
"""
Tcl shell command to create a board cutout geometry. Allow cutout for any shape. Cuts holding gaps from geometry.
# array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
aliases = ['geocutout']
example:
# Dictionary of types from Tcl command, needs to be ordered.
# For positional arguments
"""
# List of all command aliases, to be able use old
# names for backward compatibility (add_poly, add_polygon)
aliases = ['geocutout', 'geoc']
# Dictionary of types from Tcl command, needs to be ordered
arg_names = collections.OrderedDict([
('name', str)
('name', str),
])
# Dictionary of types from Tcl command, needs to be ordered.
# For options like -optionname value
# Dictionary of types from Tcl command, needs to be ordered,
# this is for options like -optionname value
option_types = collections.OrderedDict([
('dia', float),
('margin', float),
@ -30,99 +33,250 @@ class TclCommandGeoCutout(TclCommandSignaled):
# structured help for current command, args needs to be ordered
help = {
'main': "Cut holding gaps from geometry.",
'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape',
'args': collections.OrderedDict([
('name', 'Name of the geometry object.'),
('name', 'Name of the object.'),
('dia', 'Tool diameter.'),
('margin', 'Margin over bounds.'),
('gapsize', 'Size of gap.'),
('gaps', 'Type of gaps.'),
('gapsize', 'size of gap.'),
('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, "
"'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts")
]),
'examples': [" #isolate margin for example from fritzing arduino shield or any svg etc\n" +
" isolate BCu_margin -dia 3 -overlap 1\n" +
"\n" +
" #create exteriors from isolated object\n" +
" exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" +
"\n" +
" #delete isolated object if you dond need id anymore\n" +
" delete BCu_margin_iso\n" +
"\n" +
" #finally cut holding gaps\n" +
" geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"]
" isolate BCu_margin -dia 3 -overlap 1\n" +
"\n" +
" #create exteriors from isolated object\n" +
" exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" +
"\n" +
" #delete isolated object if you dond need id anymore\n" +
" delete BCu_margin_iso\n" +
"\n" +
" #finally cut holding gaps\n" +
" geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"]
}
flat_geometry = []
def execute(self, args, unnamed_args):
"""
execute current TCL shell command
:param args: array of known named arguments and options
:param unnamed_args: array of other values which were passed into command
without -somename and we do not have them in known arg_names
:return: None or exception
:param args:
:param unnamed_args:
:return:
"""
# How gaps wil be rendered:
# lr - left + right
# tb - top + bottom
# 4 - left + right +top + bottom
# 2lr - 2*left + 2*right
# 2tb - 2*top + 2*bottom
# 8 - 2*left + 2*right +2*top + 2*bottom
name = args['name']
obj = None
def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts)
def substract_rectangle_geo(geo, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
def flatten(geometry=None, reset=True, pathonly=False):
"""
Creates a list of non-iterable linear geometry objects.
Polygons are expanded into its exterior and interiors if specified.
Results are placed in flat_geometry
:param geometry: Shapely type or list or list of list of such.
:param reset: Clears the contents of self.flat_geometry.
:param pathonly: Expands polygons into linear elements.
"""
if reset:
self.flat_geometry = []
## If iterable, expand recursively.
try:
for geo in geometry:
if geo is not None:
flatten(geometry=geo,
reset=False,
pathonly=pathonly)
## Not iterable, do the actual indexing and add.
except TypeError:
if pathonly and type(geometry) == Polygon:
self.flat_geometry.append(geometry.exterior)
flatten(geometry=geometry.interiors,
reset=False,
pathonly=True)
else:
self.flat_geometry.append(geometry)
return self.flat_geometry
flat_geometry = flatten(geo, pathonly=True)
polygon = Polygon(pts)
toolgeo = cascaded_union(polygon)
diffs = []
for target in flat_geometry:
if type(target) == LineString or type(target) == LinearRing:
diffs.append(target.difference(toolgeo))
else:
log.warning("Not implemented.")
return cascaded_union(diffs)
if 'name' in args:
name = args['name']
else:
self.app.inform.emit(
"[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
return
if 'margin' in args:
margin = args['margin']
else:
margin = 0.001
if 'dia' in args:
dia = args['dia']
else:
dia = 0.1
if 'gaps' in args:
gaps = args['gaps']
else:
gaps = 4
if 'gapsize' in args:
gapsize = args['gapsize']
else:
gapsize = 0.1
# Get source object.
try:
obj = self.app.collection.get_by_name(str(name))
cutout_obj = self.app.collection.get_by_name(str(name))
except:
self.raise_tcl_error("Could not retrieve object: %s" % name)
return "Could not retrieve object: %s" % name
if 0 in {dia}:
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."
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. "
"Fill in a correct value and retry. ")
return
# Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = obj.bounds()
px = 0.5 * (xmin + xmax)
py = 0.5 * (ymin + ymax)
lenghtx = (xmax - xmin)
lenghty = (ymax - ymin)
gapsize = args['gapsize'] + (args['dia'] / 2)
xmin, ymin, xmax, ymax = cutout_obj.bounds()
px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) + margin
lenghtx = (xmax - xmin) + (margin * 2)
lenghty = (ymax - ymin) + (margin * 2)
gapsize = gapsize / 2 + (dia / 2)
try:
gaps_u = int(gaps)
except ValueError:
gaps_u = gaps
if isinstance(cutout_obj, FlatCAMGeometry):
# rename the obj name so it can be identified as cutout
cutout_obj.options["name"] += "_cutout"
if gaps_u == 8 or gaps_u == '2lr':
subtract_rectangle(cutout_obj,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if gaps_u == 8 or gaps_u == '2tb':
subtract_rectangle(cutout_obj,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
subtract_rectangle(cutout_obj,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if gaps_u == 4 or gaps_u == 'lr':
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps_u == 4 or gaps_u == 'tb':
subtract_rectangle(cutout_obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
cutout_obj.plot()
self.app.inform.emit("[success]Any-form Cutout operation finished.")
elif isinstance(cutout_obj, FlatCAMGerber):
def geo_init(geo_obj, app_obj):
try:
geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2)
except Exception as e:
log.debug("TclCommandGeoCutout.execute() --> %s" % str(e))
return 'fail'
if gaps_u == 8 or gaps_u == '2lr':
geo = substract_rectangle_geo(geo,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
geo = substract_rectangle_geo(geo,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if gaps_u == 8 or gaps_u == '2tb':
geo = substract_rectangle_geo(geo,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
geo = substract_rectangle_geo(geo,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if gaps_u == 4 or gaps_u == 'lr':
geo = substract_rectangle_geo(geo,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps_u == 4 or gaps_u == 'tb':
geo = substract_rectangle_geo(geo,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
geo_obj.solid_geometry = geo
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
cutout_obj = self.app.collection.get_by_name(outname)
else:
self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
return
if args['gaps'] == '8' or args['gaps'] == '2lr':
subtract_rectangle(obj,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
subtract_rectangle(obj,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if args['gaps'] == '8' or args['gaps'] == '2tb':
subtract_rectangle(obj,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
subtract_rectangle(obj,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if args['gaps'] == '4' or args['gaps'] == 'lr':
subtract_rectangle(obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if args['gaps'] == '4' or args['gaps'] == 'tb':
subtract_rectangle(obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)

View File

@ -12,7 +12,6 @@ import tclCommands.TclCommandAlignDrillGrid
import tclCommands.TclCommandClearShell
import tclCommands.TclCommandCncjob
import tclCommands.TclCommandCutout
import tclCommands.TclCommandCutoutAny
import tclCommands.TclCommandDelete
import tclCommands.TclCommandDrillcncjob
import tclCommands.TclCommandExportGcode