Merged in test_beta_8.907 (pull request #132)

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -106,21 +106,21 @@ class BufferSelectionTool(FlatCAMTool):
def on_buffer(self): def on_buffer(self):
buffer_distance = self.buffer_distance_entry.get_value() 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 # 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 join_style = self.buffer_corner_cb.currentIndex() + 1
self.draw_app.buffer(buffer_distance, join_style) self.draw_app.buffer(buffer_distance, join_style)
def on_buffer_int(self): def on_buffer_int(self):
buffer_distance = self.buffer_distance_entry.get_value() 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 # 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 join_style = self.buffer_corner_cb.currentIndex() + 1
self.draw_app.buffer_int(buffer_distance, join_style) self.draw_app.buffer_int(buffer_distance, join_style)
def on_buffer_ext(self): def on_buffer_ext(self):
buffer_distance = self.buffer_distance_entry.get_value() 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 # 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 join_style = self.buffer_corner_cb.currentIndex() + 1
self.draw_app.buffer_ext(buffer_distance, join_style) self.draw_app.buffer_ext(buffer_distance, join_style)
@ -583,6 +583,8 @@ class FCCircle(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_circle'
self.start_msg = "Click on CENTER ..." self.start_msg = "Click on CENTER ..."
self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"] self.steps_per_circ = self.draw_app.app.defaults["geometry_circle_steps"]
@ -620,6 +622,8 @@ class FCCircle(FCShapeTool):
class FCArc(FCShapeTool): class FCArc(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_arc'
self.start_msg = "Click on CENTER ..." self.start_msg = "Click on CENTER ..."
# Direction of rotation between point 1 and 2. # Direction of rotation between point 1 and 2.
@ -808,6 +812,8 @@ class FCRectangle(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_rectangle'
self.start_msg = "Click on 1st corner ..." self.start_msg = "Click on 1st corner ..."
def click(self, point): def click(self, point):
@ -846,6 +852,8 @@ class FCPolygon(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_polygon'
self.start_msg = "Click on 1st point ..." self.start_msg = "Click on 1st point ..."
def click(self, point): def click(self, point):
@ -891,6 +899,8 @@ class FCPath(FCPolygon):
def make(self): def make(self):
self.geometry = DrawToolShape(LineString(self.points)) self.geometry = DrawToolShape(LineString(self.points))
self.name = 'fc_path'
self.draw_app.in_action = False self.draw_app.in_action = False
self.complete = True self.complete = True
self.draw_app.app.inform.emit("[success]Done. Path completed.") self.draw_app.app.inform.emit("[success]Done. Path completed.")
@ -912,6 +922,8 @@ class FCPath(FCPolygon):
class FCSelect(DrawTool): class FCSelect(DrawTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_select'
self.storage = self.draw_app.storage self.storage = self.draw_app.storage
# self.shape_buffer = self.draw_app.shape_buffer # self.shape_buffer = self.draw_app.shape_buffer
# self.selected = self.draw_app.selected # self.selected = self.draw_app.selected
@ -989,6 +1001,7 @@ class FCSelect(DrawTool):
class FCDrillSelect(DrawTool): class FCDrillSelect(DrawTool):
def __init__(self, exc_editor_app): def __init__(self, exc_editor_app):
DrawTool.__init__(self, exc_editor_app) DrawTool.__init__(self, exc_editor_app)
self.name = 'fc_drill_select'
self.exc_editor_app = exc_editor_app self.exc_editor_app = exc_editor_app
self.storage = self.exc_editor_app.storage_dict self.storage = self.exc_editor_app.storage_dict
@ -1146,6 +1159,8 @@ class FCDrillSelect(DrawTool):
class FCMove(FCShapeTool): class FCMove(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app) FCShapeTool.__init__(self, draw_app)
self.name = 'fc_move'
# self.shape_buffer = self.draw_app.shape_buffer # self.shape_buffer = self.draw_app.shape_buffer
self.origin = None self.origin = None
self.destination = None self.destination = None
@ -1211,6 +1226,9 @@ class FCMove(FCShapeTool):
class FCCopy(FCMove): class FCCopy(FCMove):
def __init__(self, draw_app):
FCMove.__init__(self, draw_app)
self.name = 'fc_copy'
def make(self): def make(self):
# Create new geometry # Create new geometry
@ -1225,6 +1243,8 @@ class FCCopy(FCMove):
class FCText(FCShapeTool): class FCText(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app) FCShapeTool.__init__(self, draw_app)
self.name = 'fc_text'
# self.shape_buffer = self.draw_app.shape_buffer # self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app self.draw_app = draw_app
self.app = draw_app.app self.app = draw_app.app
@ -1275,6 +1295,8 @@ class FCText(FCShapeTool):
class FCBuffer(FCShapeTool): class FCBuffer(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app) FCShapeTool.__init__(self, draw_app)
self.name = 'fc_buffer'
# self.shape_buffer = self.draw_app.shape_buffer # self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app self.draw_app = draw_app
self.app = draw_app.app self.app = draw_app.app
@ -1341,6 +1363,8 @@ class FCBuffer(FCShapeTool):
class FCPaint(FCShapeTool): class FCPaint(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
FCShapeTool.__init__(self, draw_app) FCShapeTool.__init__(self, draw_app)
self.name = 'fc_paint'
# self.shape_buffer = self.draw_app.shape_buffer # self.shape_buffer = self.draw_app.shape_buffer
self.draw_app = draw_app self.draw_app = draw_app
self.app = draw_app.app self.app = draw_app.app
@ -1355,6 +1379,7 @@ class FCPaint(FCShapeTool):
class FCRotate(FCShapeTool): class FCRotate(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
FCShapeTool.__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)) 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): def set_origin(self, origin):
self.origin = origin self.origin = origin
def make(self): def make(self):
# Create new geometry # Create new geometry
# dx = self.origin[0] # dx = self.origin[0]
@ -1382,9 +1406,9 @@ class FCRotate(FCShapeTool):
#self.draw_app.select_tool("select") #self.draw_app.select_tool("select")
def on_key(self, key): def on_key(self, key):
if key == 'Enter': if key == 'Enter' or key == QtCore.Qt.Key_Enter:
if self.complete == True: self.make()
self.make() return "Done"
def click(self, point): def click(self, point):
self.make() self.make()
@ -1408,6 +1432,7 @@ class FCDrillAdd(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_add'
self.selected_dia = None self.selected_dia = None
try: try:
@ -1443,11 +1468,17 @@ class FCDrillAdd(FCShapeTool):
return DrawToolUtilityShape(self.util_shape(data)) return DrawToolUtilityShape(self.util_shape(data))
def util_shape(self, point): 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]) start_hor_line = ((point_x - (self.selected_dia / 2)), point_y)
stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1]) stop_hor_line = ((point_x + (self.selected_dia / 2)), point_y)
start_vert_line = (point[0], (point[1] - (self.selected_dia / 2))) start_vert_line = (point_x, (point_y - (self.selected_dia / 2)))
stop_vert_line = (point[0], (point[1] + (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)]) 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): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_array'
self.draw_app.array_frame.show() self.draw_app.array_frame.show()
@ -1558,28 +1590,27 @@ class FCDrillArray(FCShapeTool):
return return
if self.drill_array == 'Linear': if self.drill_array == 'Linear':
# if self.origin is None: if data[0] is None and data[1] is None:
# self.origin = (0, 0) dx = self.draw_app.x
# dy = self.draw_app.y
# dx = data[0] - self.origin[0] else:
# dy = data[1] - self.origin[1] dx = data[0]
dx = data[0] dy = data[1]
dy = data[1]
geo_list = [] geo_list = []
geo = None geo = None
self.points = data self.points = [dx, dy]
for item in range(self.drill_array_size): for item in range(self.drill_array_size):
if self.drill_axis == 'X': 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': 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': if self.drill_axis == 'A':
x_adj = self.drill_pitch * math.cos(math.radians(self.drill_linear_angle)) 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)) y_adj = self.drill_pitch * math.sin(math.radians(self.drill_linear_angle))
geo = self.util_shape( 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: if static is None or static is False:
@ -1592,16 +1623,30 @@ class FCDrillArray(FCShapeTool):
self.last_dy = dy self.last_dy = dy
return DrawToolUtilityShape(geo_list) return DrawToolUtilityShape(geo_list)
else: 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: if len(self.pt) > 0:
temp_points = [x for x in self.pt] temp_points = [x for x in self.pt]
temp_points.append(data) temp_points.append([cdx, cdy])
return DrawToolUtilityShape(LineString(temp_points)) return DrawToolUtilityShape(LineString(temp_points))
def util_shape(self, point): def util_shape(self, point):
start_hor_line = ((point[0] - (self.selected_dia / 2)), point[1]) if point[0] is None and point[1] is None:
stop_hor_line = ((point[0] + (self.selected_dia / 2)), point[1]) point_x = self.draw_app.x
start_vert_line = (point[0], (point[1] - (self.selected_dia / 2))) point_y = self.draw_app.y
stop_vert_line = (point[0], (point[1] + (self.selected_dia / 2))) 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)]) 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() self.draw_app.array_frame.hide()
return return
class FCDrillResize(FCShapeTool):
class FCDrillResize(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__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.draw_app.app.inform.emit("Click on the Drill(s) to resize ...")
self.resize_dia = None self.resize_dia = None
self.draw_app.resize_frame.show() self.draw_app.resize_frame.show()
@ -1761,6 +1808,8 @@ class FCDrillResize(FCShapeTool):
class FCDrillMove(FCShapeTool): class FCDrillMove(FCShapeTool):
def __init__(self, draw_app): def __init__(self, draw_app):
DrawTool.__init__(self, draw_app) DrawTool.__init__(self, draw_app)
self.name = 'fc_drill_move'
# self.shape_buffer = self.draw_app.shape_buffer # self.shape_buffer = self.draw_app.shape_buffer
self.origin = None self.origin = None
self.destination = None self.destination = None
@ -1850,6 +1899,9 @@ class FCDrillMove(FCShapeTool):
class FCDrillCopy(FCDrillMove): class FCDrillCopy(FCDrillMove):
def __init__(self, draw_app):
FCDrillMove.__init__(self, draw_app)
self.name = 'fc_drill_copy'
def make(self): def make(self):
# Create new geometry # Create new geometry
@ -1977,6 +2029,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.geo_key_modifiers = None self.geo_key_modifiers = None
self.x = None # Current mouse cursor pos self.x = None # Current mouse cursor pos
self.y = None self.y = None
# Current snapped mouse pos # Current snapped mouse pos
self.snap_x = None self.snap_x = None
self.snap_y = None self.snap_y = None
@ -2083,16 +2136,16 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.geo_edit_toolbar.setDisabled(True) self.app.ui.geo_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM") settings = QSettings("Open Source", "FlatCAM")
if settings.contains("theme"): if settings.contains("layout"):
theme = settings.value('theme', type=str) layout = settings.value('layout', type=str)
if theme == 'standard': if layout == 'standard':
# self.app.ui.geo_edit_toolbar.setVisible(False) # self.app.ui.geo_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False) self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False) self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False) self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.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.geo_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False) 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 # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor # 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_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_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_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.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.app.collection.view.clicked.disconnect()
self.canvas.vis_connect('mouse_press', self.on_canvas_click) 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_move', self.on_canvas_move)
self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) 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): def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) 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_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) 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 # 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_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_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_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.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) self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
def add_shape(self, shape): def add_shape(self, shape):
@ -2572,211 +2620,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.tool_shape.redraw() 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): def on_delete_btn(self):
self.delete_selected() self.delete_selected()
self.replot() self.replot()
@ -3465,7 +3308,8 @@ class FlatCAMExcEditor(QtCore.QObject):
grid1.addWidget(addtool_entry_lbl, 0, 0) grid1.addWidget(addtool_entry_lbl, 0, 0)
hlay = QtWidgets.QHBoxLayout() 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) hlay.addWidget(self.addtool_entry)
self.addtool_btn = QtWidgets.QPushButton('Add Tool') self.addtool_btn = QtWidgets.QPushButton('Add Tool')
@ -3873,7 +3717,7 @@ class FlatCAMExcEditor(QtCore.QObject):
if self.units == "IN": if self.units == "IN":
self.addtool_entry.set_value(0.039) self.addtool_entry.set_value(0.039)
else: else:
self.addtool_entry.set_value(1) self.addtool_entry.set_value(1.00)
sort_temp = [] 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 # 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) 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 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: if tool_dia not in self.olddia_newdia:
storage_elem = FlatCAMGeoEditor.make_storage() storage_elem = FlatCAMGeoEditor.make_storage()
@ -4224,16 +4080,16 @@ class FlatCAMExcEditor(QtCore.QObject):
self.app.ui.exc_edit_toolbar.setDisabled(True) self.app.ui.exc_edit_toolbar.setDisabled(True)
settings = QSettings("Open Source", "FlatCAM") settings = QSettings("Open Source", "FlatCAM")
if settings.contains("theme"): if settings.contains("layout"):
theme = settings.value('theme', type=str) layout = settings.value('layout', type=str)
if theme == 'standard': if layout == 'standard':
# self.app.ui.exc_edit_toolbar.setVisible(False) # self.app.ui.exc_edit_toolbar.setVisible(False)
self.app.ui.snap_max_dist_entry.setEnabled(False) self.app.ui.snap_max_dist_entry.setEnabled(False)
self.app.ui.corner_snap_btn.setEnabled(False) self.app.ui.corner_snap_btn.setEnabled(False)
self.app.ui.snap_magnet.setVisible(False) self.app.ui.snap_magnet.setVisible(False)
self.app.ui.corner_snap_btn.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.exc_edit_toolbar.setVisible(True)
self.app.ui.snap_max_dist_entry.setEnabled(False) 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 # make sure that the shortcuts key and mouse events will no longer be linked to the methods from FlatCAMApp
# but those from FlatCAMGeoEditor # 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_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_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_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.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.app.collection.view.clicked.disconnect()
self.canvas.vis_connect('mouse_press', self.on_canvas_click) 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_move', self.on_canvas_move)
self.canvas.vis_connect('mouse_release', self.on_canvas_click_release) 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): def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click) 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_move', self.on_canvas_move)
self.canvas.vis_disconnect('mouse_release', self.on_canvas_click_release) 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 # 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_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_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_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.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) self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
def clear(self): def clear(self):
@ -4374,6 +4222,9 @@ class FlatCAMExcEditor(QtCore.QObject):
self.replot() self.replot()
# add a first tool in the Tool Table
self.on_tool_add(tooldia=1.00)
def update_fcexcellon(self, exc_obj): def update_fcexcellon(self, exc_obj):
""" """
Create a new Excellon object that contain the edited content of the source Excellon object Create a new Excellon object that contain the edited content of the source Excellon object
@ -4871,145 +4722,6 @@ class FlatCAMExcEditor(QtCore.QObject):
# Update cursor # Update cursor
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black', size=20) 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): def on_canvas_key_release(self, event):
self.key = None self.key = None

File diff suppressed because it is too large Load Diff

View File

@ -388,7 +388,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry) grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
def __init__(self, name): 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) FlatCAMObj.__init__(self, name)
self.kind = "gerber" self.kind = "gerber"
@ -480,7 +480,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def geo_init(geo_obj, app_obj): def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry) 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"]: if not self.options["noncopperrounded"]:
bounding_box = bounding_box.envelope bounding_box = bounding_box.envelope
non_copper = bounding_box.difference(self.solid_geometry) non_copper = bounding_box.difference(self.solid_geometry)
@ -497,7 +497,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def geo_init(geo_obj, app_obj): def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry) assert isinstance(geo_obj, FlatCAMGeometry)
# Bounding box with rounded corners # 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 if not self.options["bboxrounded"]: # Remove rounded corners
bounding_box = bounding_box.envelope bounding_box = bounding_box.envelope
geo_obj.solid_geometry = bounding_box geo_obj.solid_geometry = bounding_box
@ -557,7 +557,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def follow_init(follow_obj, app): def follow_init(follow_obj, app):
# Propagate options # Propagate options
follow_obj.options["cnctooldia"] = self.options["isotooldia"] follow_obj.options["cnctooldia"] = float(self.options["isotooldia"])
follow_obj.solid_geometry = self.solid_geometry follow_obj.solid_geometry = self.solid_geometry
# TODO: Do something if this is None. Offer changing name? # TODO: Do something if this is None. Offer changing name?
@ -579,11 +579,11 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
:return: None :return: None
""" """
if dia is None: if dia is None:
dia = self.options["isotooldia"] dia = float(self.options["isotooldia"])
if passes is None: if passes is None:
passes = int(self.options["isopasses"]) passes = int(self.options["isopasses"])
if overlap is None: if overlap is None:
overlap = self.options["isooverlap"] overlap = float(self.options["isooverlap"])
if combine is None: if combine is None:
combine = self.options["combine_passes"] combine = self.options["combine_passes"]
else: else:
@ -638,7 +638,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# TODO: This is ugly. Create way to pass data into init function. # TODO: This is ugly. Create way to pass data into init function.
def iso_init(geo_obj, app_obj): def iso_init(geo_obj, app_obj):
# Propagate options # Propagate options
geo_obj.options["cnctooldia"] = self.options["isotooldia"] geo_obj.options["cnctooldia"] = float(self.options["isotooldia"])
geo_obj.solid_geometry = [] geo_obj.solid_geometry = []
for i in range(passes): for i in range(passes):
iso_offset = (((2 * i + 1) / 2.0) * dia) - (i * overlap * dia) 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. # TODO: This is ugly. Create way to pass data into init function.
def iso_init(geo_obj, app_obj): def iso_init(geo_obj, app_obj):
# Propagate options # 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 is climb then the move is counter-clockwise around features
if milling_type == 'cl': if milling_type == 'cl':
@ -753,8 +753,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
factor = Gerber.convert_units(self, units) factor = Gerber.convert_units(self, units)
self.options['isotooldia'] *= factor self.options['isotooldia'] = float(self.options['isotooldia']) * factor
self.options['bboxmargin'] *= factor self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor
def plot(self, **kwargs): def plot(self, **kwargs):
""" """
@ -833,7 +833,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
optionChanged = QtCore.pyqtSignal(str) optionChanged = QtCore.pyqtSignal(str)
def __init__(self, name): 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) FlatCAMObj.__init__(self, name)
self.kind = "excellon" self.kind = "excellon"
@ -1458,7 +1458,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
outname = self.options["name"] + "_mill" outname = self.options["name"] + "_mill"
if tooldia is None: if tooldia is None:
tooldia = self.options["tooldia"] tooldia = float(self.options["tooldia"])
# Sort tools by diameter. items() -> [('name', diameter), ...] # Sort tools by diameter. items() -> [('name', diameter), ...]
# sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 # 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" outname = self.options["name"] + "_mill"
if tooldia is None: if tooldia is None:
tooldia = self.options["slot_tooldia"] tooldia = float(self.options["slot_tooldia"])
# Sort tools by diameter. items() -> [('name', diameter), ...] # Sort tools by diameter. items() -> [('name', diameter), ...]
# sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 # 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." return False, "Error: No tools."
for tool in 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.") 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." 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" # 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: for slot in self.slots:
if slot['tool'] in tools: 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: if buffer_value == 0:
start = slot['start'] start = slot['start']
stop = slot['stop'] stop = slot['stop']
lines_string = LineString([start, 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) geo_obj.solid_geometry.append(poly)
else: else:
start = slot['start'] start = slot['start']
stop = slot['stop'] stop = slot['stop']
lines_string = LineString([start, 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) geo_obj.solid_geometry.append(poly)
if use_thread: if use_thread:
@ -1685,15 +1693,16 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
job_obj.options['ppname_e'] = pp_excellon_name job_obj.options['ppname_e'] = pp_excellon_name
app_obj.progress.emit(20) app_obj.progress.emit(20)
job_obj.z_cut = self.options["drillz"] job_obj.z_cut = float(self.options["drillz"])
job_obj.z_move = self.options["travelz"] job_obj.z_move = float(self.options["travelz"])
job_obj.feedrate = self.options["feedrate"] job_obj.feedrate = float(self.options["feedrate"])
job_obj.feedrate_rapid = self.options["feedrate_rapid"] job_obj.feedrate_rapid = float(self.options["feedrate_rapid"])
job_obj.spindlespeed = self.options["spindlespeed"]
job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] else None
job_obj.dwell = self.options["dwell"] 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.pp_excellon_name = pp_excellon_name
job_obj.toolchange_xy = self.app.defaults["excellon_toolchangexy"]
job_obj.toolchange_xy_type = "excellon" job_obj.toolchange_xy_type = "excellon"
job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"]) job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"])
job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"]) job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"])
@ -1729,14 +1738,18 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# job_obj.options["tooldia"] = # job_obj.options["tooldia"] =
tools_csv = ','.join(tools) tools_csv = ','.join(tools)
job_obj.generate_from_excellon_by_tool(self, tools_csv, ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv,
drillz=self.options['drillz'], drillz=float(self.options['drillz']),
toolchange=self.options["toolchange"], toolchange=float(self.options["toolchange"]),
toolchangez=self.options["toolchangez"], toolchangexy=self.app.defaults["excellon_toolchangexy"],
startz=self.options["startz"], toolchangez=float(self.options["toolchangez"]),
endz=self.options["endz"], startz=float(self.options["startz"]) if
excellon_optimization_type=self.options["optimization_type"]) 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) app_obj.progress.emit(50)
job_obj.gcode_parse() job_obj.gcode_parse()
@ -1772,11 +1785,11 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def convert_units(self, units): def convert_units(self, units):
factor = Excellon.convert_units(self, units) factor = Excellon.convert_units(self, units)
self.options['drillz'] *= factor self.options['drillz'] = float(self.options['drillz']) * factor
self.options['travelz'] *= factor self.options['travelz'] = float(self.options['travelz']) * factor
self.options['feedrate'] *= factor self.options['feedrate'] = float(self.options['feedrate']) * factor
self.options['feedrate_rapid'] *= factor self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
self.options['toolchangez'] *= factor self.options['toolchangez'] = float(self.options['toolchangez']) * factor
if self.app.defaults["excellon_toolchangexy"] == '': if self.app.defaults["excellon_toolchangexy"] == '':
self.options['toolchangexy'] = "0.0, 0.0" 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]) self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
if self.options['startz'] is not None: if self.options['startz'] is not None:
self.options['startz'] *= factor self.options['startz'] = float(self.options['startz']) * factor
self.options['endz'] *= factor self.options['endz'] = float(self.options['endz']) * factor
def plot(self): def plot(self):
@ -1967,7 +1980,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
def __init__(self, name): def __init__(self, name):
FlatCAMObj.__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" self.kind = "geometry"
@ -2260,7 +2273,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
if not self.tools: if not self.tools:
self.tools.update({ self.tools.update({
self.tooluid: { self.tooluid: {
'tooldia': self.options["cnctooldia"], 'tooldia': float(self.options["cnctooldia"]),
'offset': 'Path', 'offset': 'Path',
'offset_value': 0.0, 'offset_value': 0.0,
'type': 'Rough', 'type': 'Rough',
@ -2805,8 +2818,28 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
self.ui.cutz_entry.setDisabled(False) self.ui.cutz_entry.setDisabled(False)
def update_cutz(self): def update_cutz(self):
vdia = float(self.ui.tipdia_entry.get_value()) try:
half_vangle = float(self.ui.tipangle_entry.get_value()) / 2 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() row = self.ui.geo_tools_table.currentRow()
tool_uid = int(self.ui.geo_tools_table.item(row, 5).text()) 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']) 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']) segy = segy if segy is not None else float(self.app.defaults['geometry_segy'])
xmin = self.options['xmin'] try:
ymin = self.options['ymin'] xmin = self.options['xmin']
xmax = self.options['xmax'] ymin = self.options['ymin']
ymax = self.options['ymax'] 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() # Object initialization function for app.new_object()
# RUNNING ON SEPARATE THREAD! # RUNNING ON SEPARATE THREAD!
@ -3349,6 +3390,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
job_obj.multigeo = True job_obj.multigeo = True
job_obj.cnc_tools.clear() 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: try:
job_obj.z_pdepth = float(self.options["z_pdepth"]) job_obj.z_pdepth = float(self.options["z_pdepth"])
except ValueError: except ValueError:
@ -3591,36 +3637,36 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
:return: None :return: None
""" """
tooldia = tooldia if tooldia else self.options["cnctooldia"] tooldia = tooldia if tooldia else float(self.options["cnctooldia"])
outname = outname if outname is not None else self.options["name"] 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_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 self.options["travelz"] 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 = feedrate if feedrate is not None else float(self.options["feedrate"])
feedrate_z = feedrate_z if feedrate_z is not None else self.options["feedrate_z"] 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 self.options["feedrate_rapid"] 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"] 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']) 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']) 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"] extracut = extracut if extracut is not None else float(self.options["extracut"])
startz = startz if startz is not None else self.options["startz"] startz = startz if startz is not None else float(self.options["startz"])
endz = endz if endz is not None else self.options["endz"] 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"] toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"]
toolchange = toolchange if toolchange else self.options["toolchange"] toolchange = toolchange if toolchange else self.options["toolchange"]
offset = offset if offset else 0.0 offset = offset if offset else 0.0
# int or None. # 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"] 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"] 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) factor = Geometry.convert_units(self, units)
self.options['cutz'] *= factor self.options['cutz'] = float(self.options['cutz']) * factor
self.options['depthperpass'] *= factor self.options['depthperpass'] = float(self.options['depthperpass']) * factor
self.options['travelz'] *= factor self.options['travelz'] = float(self.options['travelz']) * factor
self.options['feedrate'] *= factor self.options['feedrate'] = float(self.options['feedrate']) * factor
self.options['feedrate_z'] *= factor self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor
self.options['feedrate_rapid'] *= factor self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor
self.options['endz'] *= factor self.options['endz'] = float(self.options['endz']) * factor
# self.options['cnctooldia'] *= factor # self.options['cnctooldia'] *= factor
# self.options['painttooldia'] *= factor # self.options['painttooldia'] *= factor
# self.options['paintmargin'] *= factor # self.options['paintmargin'] *= factor
# self.options['paintoverlap'] *= factor # self.options['paintoverlap'] *= factor
self.options["toolchangez"] *= factor self.options["toolchangez"] = float(self.options["toolchangez"]) * factor
if self.app.defaults["geometry_toolchangexy"] == '': if self.app.defaults["geometry_toolchangexy"] == '':
self.options['toolchangexy'] = "0.0, 0.0" 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]) self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1])
if self.options['startz'] is not None: 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', param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
'endz', 'toolchangez'] 'endz', 'toolchangez']
@ -4015,7 +4061,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
CNCjob.__init__(self, units=units, kind=kind, z_move=z_move, CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia, 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) FlatCAMObj.__init__(self, name)
@ -4212,7 +4258,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.to_form() self.to_form()
# set the kind of geometries are plotted by default with plot2() from camlib.CNCJob # 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.updateplot_button.clicked.connect(self.on_updateplot_button_click)
self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_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 (*.*)" "G-Code Files (*.g-code);;All Files (*.*)"
try: 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 ...", caption="Export Machine Code ...",
directory=self.app.get_last_save_folder() + '/' + name, directory=dir_file_to_save,
filter=_filter_ filter=_filter_
)[0]) )
except TypeError: 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 == '': if filename == '':
self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...") self.app.inform.emit("[WARNING_NOTCL]Export Machine Code cancelled ...")
@ -4301,9 +4350,14 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.app.ui.code_editor.clear() self.app.ui.code_editor.clear()
# then append the text from GCode to the text editor # then append the text from GCode to the text editor
for line in self.app.gcode_edited: try:
proc_line = str(line).strip('\n') for line in self.app.gcode_edited:
self.app.ui.code_editor.append(proc_line) 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) self.app.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
@ -4472,6 +4526,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
elif to_file is False: elif to_file is False:
# Just for adding it to the recent files list. # Just for adding it to the recent files list.
self.app.file_opened.emit("cncjob", filename) self.app.file_opened.emit("cncjob", filename)
self.app.file_saved.emit("cncjob", filename)
self.app.inform.emit("[success] Saved to: " + filename) self.app.inform.emit("[success] Saved to: " + filename)
else: else:
@ -4549,7 +4604,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
try: try:
if self.multitool is False: # single tool usage 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: else:
# multiple tools usage # multiple tools usage
for tooluid_key in self.cnc_tools: for tooluid_key in self.cnc_tools:
@ -4564,7 +4619,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
def convert_units(self, units): def convert_units(self, units):
factor = CNCjob.convert_units(self, units) factor = CNCjob.convert_units(self, units)
FlatCAMApp.App.log.debug("FlatCAMCNCjob.convert_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', param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid',
'endz', 'toolchangez'] 'endz', 'toolchangez']

View File

@ -83,6 +83,9 @@ class FlatCAMTool(QtWidgets.QWidget):
# Put ourself in the GUI # Put ourself in the GUI
self.app.ui.tool_scroll_area.setWidget(self) 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 # Switch notebook to tool page
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)

View File

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

View File

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

View File

@ -611,7 +611,8 @@ class ExcellonObjectUI(ObjectUI):
self.tools_box.addLayout(gcode_box) self.tools_box.addLayout(gcode_box)
# temporary action until I finish the feature # 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 = QtWidgets.QPushButton('Create GCode')
self.generate_cnc_button.setToolTip( self.generate_cnc_button.setToolTip(
@ -783,7 +784,8 @@ class GeometryObjectUI(ObjectUI):
"cut and negative for 'inside' cut." "cut and negative for 'inside' cut."
) )
self.grid1.addWidget(self.tool_offset_lbl, 0, 0) self.grid1.addWidget(self.tool_offset_lbl, 0, 0)
self.tool_offset_entry = FCEntry() self.tool_offset_entry = FloatEntry()
self.tool_offset_entry.setValidator(QtGui.QDoubleValidator(-9999.9999, 9999.9999, 4))
spacer_lbl = QtWidgets.QLabel(" ") spacer_lbl = QtWidgets.QLabel(" ")
spacer_lbl.setFixedWidth(80) spacer_lbl.setFixedWidth(80)
@ -1129,9 +1131,14 @@ class CNCObjectUI(ObjectUI):
{"label": "Cut", "value": "cut"} {"label": "Cut", "value": "cut"}
], stretch=False) ], 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) 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('') e1_lbl = QtWidgets.QLabel('')
self.custom_box.addWidget(e1_lbl) self.custom_box.addWidget(e1_lbl)

View File

@ -9,9 +9,64 @@ CAD program, and create G-Code for Isolation routing.
================================================= =================================================
9.02.2019
- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory
- added into Preferences the Calculator Tools
- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before)
- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor
- changed the messages from status bar on new object creation/selection
- in Geometry Editor fixed the handler for the Rotate shortcut key ('R')
8.02.2019
- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible
- changed the menu entry Toggle Grid name to Toggle Grid Snap
- fixed errors in Toggle Axis
- fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View
- moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI
- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
- changed the shortcut key for Transform Tool from 'T' to 'ALT+T'
- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
- added Double Validator for the Offset value so only float numbers can be entered.
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab
- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value.
7.02.2019
- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object
- commented some debug messages
- imported speedups for shapely
- added a disable menu entry in the canvas contextual menu
- small changes in Tools layout
- added some new icons in the help menu and reorganized this menu
- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section
- changed the Shortcut list shortcut key to F3
- moved some graphical classes out of Tool Shell to GUIElements.py where they belong
- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed
- in Move Tool I've added the type of object that was moved in the status bar message
- color coded the status bar bullet to blue for selection
- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs.
6.02.2019 6.02.2019
- fixed the units calculators crash FlatCAM when using comma as decimal separator - 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 5.02.3019

468
camlib.py
View File

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

View File

@ -21,6 +21,40 @@ class ToolCalculator(FlatCAMTool):
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName) title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
self.layout.addWidget(title_label) self.layout.addWidget(title_label)
######################
## 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 ## ## V-shape Tool Calculator ##
############################ ############################
@ -83,38 +117,6 @@ class ToolCalculator(FlatCAMTool):
form_layout.addRow(self.empty_label, self.calculate_vshape_button) 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 ## ## ElectroPlating Tool Calculator ##
@ -224,27 +226,31 @@ class ToolCalculator(FlatCAMTool):
FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs) FlatCAMTool.install(self, icon, separator, shortcut='ALT+C', **kwargs)
def set_tool_ui(self): def set_tool_ui(self):
self.units = self.app.general_options_form.general_app_group.units_radio.get_value().upper()
## Initialize form ## Initialize form
self.mm_entry.set_value('0') self.mm_entry.set_value('0')
self.inch_entry.set_value('0') self.inch_entry.set_value('0')
self.pcblength_entry.set_value('10') length = self.app.defaults["tools_calc_electro_length"]
self.pcbwidth_entry.set_value('10') width = self.app.defaults["tools_calc_electro_width"]
self.cdensity_entry.set_value('13') density = self.app.defaults["tools_calc_electro_cdensity"]
self.growth_entry.set_value('10') growth = self.app.defaults["tools_calc_electro_growth"]
self.cvalue_entry.set_value(2.80) self.pcblength_entry.set_value(length)
self.time_entry.set_value(33.0) 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': tip_dia = self.app.defaults["tools_calc_vshape_tip_dia"]
self.tipDia_entry.set_value('0.2') tip_angle = self.app.defaults["tools_calc_vshape_tip_angle"]
self.tipAngle_entry.set_value('45') cut_z = self.app.defaults["tools_calc_vshape_cut_z"]
self.cutDepth_entry.set_value('0.25')
self.effectiveToolDia_entry.set_value('0.39') self.tipDia_entry.set_value(tip_dia)
else: self.tipAngle_entry.set_value(tip_angle)
self.tipDia_entry.set_value('7.87402') self.cutDepth_entry.set_value(cut_z)
self.tipAngle_entry.set_value('45') self.effectiveToolDia_entry.set_value('0.0000')
self.cutDepth_entry.set_value('9.84252')
self.effectiveToolDia_entry.set_value('15.35433')
def on_calculate_tool_dia(self): def on_calculate_tool_dia(self):
# Calculation: # Calculation:

View File

@ -184,25 +184,22 @@ class Measurement(FlatCAMTool):
# disconnect the mouse/key events from functions of measurement tool # 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_move', self.on_mouse_move_meas)
self.app.plotcanvas.vis_disconnect('mouse_press', self.on_click_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 # reconnect the mouse/key events to the functions from where the tool was called
if self.app.call_source == 'app': 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_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('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) self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor': 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_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('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_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) self.app.geo_editor.canvas.vis_connect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor': 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_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('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_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.app.exc_editor.canvas.vis_connect('mouse_release', self.app.exc_editor.on_canvas_click_release)
self.clicked_meas = 0 self.clicked_meas = 0
@ -219,19 +216,17 @@ class Measurement(FlatCAMTool):
if self.app.call_source == 'app': 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_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('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) self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
elif self.app.call_source == 'geo_editor': 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_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('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_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) self.app.geo_editor.canvas.vis_disconnect('mouse_release', self.app.geo_editor.on_canvas_click_release)
elif self.app.call_source == 'exc_editor': 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_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('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_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) 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 # we can safely connect the app mouse events to the measurement tool

View File

@ -47,7 +47,7 @@ class ToolMove(FlatCAMTool):
self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move) self.app.plotcanvas.vis_disconnect('mouse_move', self.on_move)
self.app.plotcanvas.vis_disconnect('mouse_press', self.on_left_click) 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_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 self.clicked_move = 0
@ -139,12 +139,13 @@ class ToolMove(FlatCAMTool):
proc.done() proc.done()
# delete the selection bounding box # delete the selection bounding box
self.delete_shape() 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.app.worker_task.emit({'fcn': job_move, 'params': [self]})
self.clicked_move = 0 self.clicked_move = 0
self.toggle() self.toggle()
self.app.inform.emit("[success]Object was moved ...")
return return
except TypeError: except TypeError:

View File

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

View File

@ -750,7 +750,9 @@ class ToolPaint(FlatCAMTool, Gerber):
overlap=overlap, overlap=overlap,
connect=connect, connect=connect,
contour=contour) 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) self.app.plotcanvas.vis_connect('mouse_press', doit)
def paint_poly(self, obj, inside_pt, tooldia, overlap, def paint_poly(self, obj, inside_pt, tooldia, overlap,
@ -839,6 +841,17 @@ class ToolPaint(FlatCAMTool, Gerber):
return None return None
geo_obj.solid_geometry = [] 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: try:
poly_buf = poly.buffer(-paint_margin) poly_buf = poly.buffer(-paint_margin)
if isinstance(poly_buf, MultiPolygon): 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.append(float(self.tools_table.item(row, 1).text()))
sorted_tools.sort(reverse=True) 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 = [] total_geometry = []
current_uid = int(1) current_uid = int(1)
geo_obj.solid_geometry = [] geo_obj.solid_geometry = []
@ -1085,6 +1108,16 @@ class ToolPaint(FlatCAMTool, Gerber):
current_uid = int(1) current_uid = int(1)
geo_obj.solid_geometry = [] 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 tool_dia in sorted_tools:
for geo in recurse(obj.solid_geometry): for geo in recurse(obj.solid_geometry):
try: try:

View File

@ -6,165 +6,12 @@
# MIT Licence # # 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 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): class TermWidget(QWidget):
@ -234,7 +81,7 @@ class TermWidget(QWidget):
""" """
Convert text to HTML for inserting it to browser Convert text to HTML for inserting it to browser
""" """
assert style in ('in', 'out', 'err', 'warning', 'success') assert style in ('in', 'out', 'err', 'warning', 'success', 'selected')
text = html.escape(text) text = html.escape(text)
text = text.replace('\n', '<br/>') 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 text = '<span style="font-weight: bold; color: rgb(244, 182, 66);">%s</span>' % text
elif style == 'success': elif style == 'success':
text = '<span style="font-weight: bold; color: rgb(8, 68, 0);">%s</span>' % text 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: else:
text = '<span>%s</span>' % text # without span <br/> is ignored!!! text = '<span>%s</span>' % text # without span <br/> is ignored!!!
@ -313,6 +162,11 @@ class TermWidget(QWidget):
""" """
self._append_to_browser('success', text) 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): def append_warning(self, text):
"""Appent text to output widget """Appent text to output widget
""" """
@ -352,6 +206,7 @@ class TermWidget(QWidget):
self._edit.setPlainText(self._history[self._historyIndex]) self._edit.setPlainText(self._history[self._historyIndex])
self._edit.moveCursor(QTextCursor.End) self._edit.moveCursor(QTextCursor.End)
class FCShell(TermWidget): class FCShell(TermWidget):
def __init__(self, sysShell, version, *args): def __init__(self, sysShell, version, *args):
TermWidget.__init__(self, version, *args) TermWidget.__init__(self, version, *args)

View File

@ -45,7 +45,7 @@ class default(FlatCAMPostProc):
gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n' gcode += '(Steps per circle: ' + str(p['steps_per_circle']) + ')\n'
if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry':
gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' gcode += '(Postprocessor Excellon: ' + str(p['pp_excellon_name']) + ')\n' + '\n'
else: else:
gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n' gcode += '(Postprocessor Geometry: ' + str(p['pp_geometry_name']) + ')\n' + '\n'

BIN
share/about32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
share/bluelight12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

BIN
share/letter_t_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

BIN
share/youtube32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

View File

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

View File

@ -1,23 +1,26 @@
from ObjectCollection import * from ObjectCollection import *
from tclCommands.TclCommand import TclCommandSignaled from tclCommands.TclCommand import TclCommandSignaled
from copy import deepcopy
class TclCommandGeoCutout(TclCommandSignaled): 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) example:
aliases = ['geocutout']
# 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([ arg_names = collections.OrderedDict([
('name', str) ('name', str),
]) ])
# Dictionary of types from Tcl command, needs to be ordered. # Dictionary of types from Tcl command, needs to be ordered,
# For options like -optionname value # this is for options like -optionname value
option_types = collections.OrderedDict([ option_types = collections.OrderedDict([
('dia', float), ('dia', float),
('margin', float), ('margin', float),
@ -30,99 +33,250 @@ class TclCommandGeoCutout(TclCommandSignaled):
# structured help for current command, args needs to be ordered # structured help for current command, args needs to be ordered
help = { help = {
'main': "Cut holding gaps from geometry.", 'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape',
'args': collections.OrderedDict([ 'args': collections.OrderedDict([
('name', 'Name of the geometry object.'), ('name', 'Name of the object.'),
('dia', 'Tool diameter.'), ('dia', 'Tool diameter.'),
('margin', 'Margin over bounds.'), ('margin', 'Margin over bounds.'),
('gapsize', 'Size of gap.'), ('gapsize', 'size of gap.'),
('gaps', 'Type of gaps.'), ('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" + 'examples': [" #isolate margin for example from fritzing arduino shield or any svg etc\n" +
" isolate BCu_margin -dia 3 -overlap 1\n" + " isolate BCu_margin -dia 3 -overlap 1\n" +
"\n" + "\n" +
" #create exteriors from isolated object\n" + " #create exteriors from isolated object\n" +
" exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" + " exteriors BCu_margin_iso -outname BCu_margin_iso_exterior\n" +
"\n" + "\n" +
" #delete isolated object if you dond need id anymore\n" + " #delete isolated object if you dond need id anymore\n" +
" delete BCu_margin_iso\n" + " delete BCu_margin_iso\n" +
"\n" + "\n" +
" #finally cut holding gaps\n" + " #finally cut holding gaps\n" +
" geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"] " geocutout BCu_margin_iso_exterior -dia 3 -gapsize 0.6 -gaps 4\n"]
} }
flat_geometry = []
def execute(self, args, unnamed_args): def execute(self, args, unnamed_args):
""" """
execute current TCL shell command
:param args: array of known named arguments and options :param args:
:param unnamed_args: array of other values which were passed into command :param unnamed_args:
without -somename and we do not have them in known arg_names :return:
:return: None or exception
""" """
# 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): def subtract_rectangle(obj_, x0, y0, x1, y1):
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
obj_.subtract_polygon(pts) 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: try:
obj = self.app.collection.get_by_name(str(name)) cutout_obj = self.app.collection.get_by_name(str(name))
except: 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 # Get min and max data for each object as we just cut rectangles across X or Y
xmin, ymin, xmax, ymax = obj.bounds() xmin, ymin, xmax, ymax = cutout_obj.bounds()
px = 0.5 * (xmin + xmax) px = 0.5 * (xmin + xmax) + margin
py = 0.5 * (ymin + ymax) py = 0.5 * (ymin + ymax) + margin
lenghtx = (xmax - xmin) lenghtx = (xmax - xmin) + (margin * 2)
lenghty = (ymax - ymin) lenghty = (ymax - ymin) + (margin * 2)
gapsize = args['gapsize'] + (args['dia'] / 2)
gapsize = gapsize / 2 + (dia / 2)
try:
gaps_u = int(gaps)
except ValueError:
gaps_u = gaps
if isinstance(cutout_obj, FlatCAMGeometry):
# rename the obj name so it can be identified as cutout
cutout_obj.options["name"] += "_cutout"
if gaps_u == 8 or gaps_u == '2lr':
subtract_rectangle(cutout_obj,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if gaps_u == 8 or gaps_u == '2tb':
subtract_rectangle(cutout_obj,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
subtract_rectangle(cutout_obj,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if gaps_u == 4 or gaps_u == 'lr':
subtract_rectangle(cutout_obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps_u == 4 or gaps_u == 'tb':
subtract_rectangle(cutout_obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
cutout_obj.plot()
self.app.inform.emit("[success]Any-form Cutout operation finished.")
elif isinstance(cutout_obj, FlatCAMGerber):
def geo_init(geo_obj, app_obj):
try:
geo = cutout_obj.isolation_geometry((dia / 2), iso_type=0, corner=2)
except Exception as e:
log.debug("TclCommandGeoCutout.execute() --> %s" % str(e))
return 'fail'
if gaps_u == 8 or gaps_u == '2lr':
geo = substract_rectangle_geo(geo,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
geo = substract_rectangle_geo(geo,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if gaps_u == 8 or gaps_u == '2tb':
geo = substract_rectangle_geo(geo,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
geo = substract_rectangle_geo(geo,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if gaps_u == 4 or gaps_u == 'lr':
geo = substract_rectangle_geo(geo,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if gaps_u == 4 or gaps_u == 'tb':
geo = substract_rectangle_geo(geo,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)
geo_obj.solid_geometry = geo
outname = cutout_obj.options["name"] + "_cutout"
self.app.new_object('geometry', outname, geo_init)
cutout_obj = self.app.collection.get_by_name(outname)
else:
self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
return
if args['gaps'] == '8' or args['gaps'] == '2lr':
subtract_rectangle(obj,
xmin - gapsize, # botleft_x
py - gapsize + lenghty / 4, # botleft_y
xmax + gapsize, # topright_x
py + gapsize + lenghty / 4) # topright_y
subtract_rectangle(obj,
xmin - gapsize,
py - gapsize - lenghty / 4,
xmax + gapsize,
py + gapsize - lenghty / 4)
if args['gaps'] == '8' or args['gaps'] == '2tb':
subtract_rectangle(obj,
px - gapsize + lenghtx / 4,
ymin - gapsize,
px + gapsize + lenghtx / 4,
ymax + gapsize)
subtract_rectangle(obj,
px - gapsize - lenghtx / 4,
ymin - gapsize,
px + gapsize - lenghtx / 4,
ymax + gapsize)
if args['gaps'] == '4' or args['gaps'] == 'lr':
subtract_rectangle(obj,
xmin - gapsize,
py - gapsize,
xmax + gapsize,
py + gapsize)
if args['gaps'] == '4' or args['gaps'] == 'tb':
subtract_rectangle(obj,
px - gapsize,
ymin - gapsize,
px + gapsize,
ymax + gapsize)

View File

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