commit
c59da8669b
|
@ -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_())
|
||||
|
||||
|
|
769
FlatCAMApp.py
769
FlatCAMApp.py
File diff suppressed because it is too large
Load Diff
514
FlatCAMEditor.py
514
FlatCAMEditor.py
|
@ -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
|
||||
|
||||
|
|
965
FlatCAMGUI.py
965
FlatCAMGUI.py
File diff suppressed because it is too large
Load Diff
217
FlatCAMObj.py
217
FlatCAMObj.py
|
@ -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']
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
172
GUIElements.py
172
GUIElements.py
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
15
ObjectUI.py
15
ObjectUI.py
|
@ -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)
|
||||
|
|
55
README.md
55
README.md
|
@ -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
468
camlib.py
|
@ -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 == '':
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 313 B |
Binary file not shown.
After Width: | Height: | Size: 204 B |
Binary file not shown.
After Width: | Height: | Size: 637 B |
|
@ -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.")
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue