commit
c59da8669b
|
@ -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_())
|
||||||
|
|
||||||
|
|
769
FlatCAMApp.py
769
FlatCAMApp.py
File diff suppressed because it is too large
Load Diff
514
FlatCAMEditor.py
514
FlatCAMEditor.py
|
@ -106,21 +106,21 @@ class BufferSelectionTool(FlatCAMTool):
|
||||||
def on_buffer(self):
|
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
|
||||||
|
|
||||||
|
|
965
FlatCAMGUI.py
965
FlatCAMGUI.py
File diff suppressed because it is too large
Load Diff
217
FlatCAMObj.py
217
FlatCAMObj.py
|
@ -388,7 +388,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry)
|
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']
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
172
GUIElements.py
172
GUIElements.py
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
15
ObjectUI.py
15
ObjectUI.py
|
@ -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)
|
||||||
|
|
55
README.md
55
README.md
|
@ -9,9 +9,64 @@ CAD program, and create G-Code for Isolation routing.
|
||||||
|
|
||||||
=================================================
|
=================================================
|
||||||
|
|
||||||
|
9.02.2019
|
||||||
|
|
||||||
|
- added a protection for when saving a file first time, it require a saved path and if none then it use the current working directory
|
||||||
|
- added into Preferences the Calculator Tools
|
||||||
|
- made the Preferences window scrollable on the horizontal side (it was only vertically scrollable before)
|
||||||
|
- fixed an error in Excellon Editor -> add drill array that could appear by starting the function to add a drill array by shortcut before any mouse move is registered while in Editor
|
||||||
|
- changed the messages from status bar on new object creation/selection
|
||||||
|
- in Geometry Editor fixed the handler for the Rotate shortcut key ('R')
|
||||||
|
|
||||||
|
8.02.2019
|
||||||
|
|
||||||
|
- when shortcut keys 1, 2, 3 (tab selection) are activated, if the splitter left side (the notebook) is hidden it will be mae visible
|
||||||
|
- changed the menu entry Toggle Grid name to Toggle Grid Snap
|
||||||
|
- fixed errors in Toggle Axis
|
||||||
|
- fixed error with shortcut key triggering twice the keyPressEvent when in the Project List View
|
||||||
|
- moved all shortcut keys handlers from Editors to the keyPressEvent() handler from FLatCAMGUI
|
||||||
|
- in Excellon Editor added a protection for Tool_dia field in case numbers using comma as decimal separator are used. Also added a QDoubleValidator forcing a number with max 4 decimals and from 0.0000 to 9.9999
|
||||||
|
- in Excellon Editor added a shortcut key 'T' that popup a window allowing to enter a new Tool with the set diameter
|
||||||
|
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Selected tab is on focus and only if a Geometry object is selected
|
||||||
|
- changed the shortcut key for Transform Tool from 'T' to 'ALT+T'
|
||||||
|
- fixed bug in Geometry Selected tab that generated error when used tool offset was less than half of either total length or half of total width. Now the app signal the issue with a status bar message
|
||||||
|
- added Double Validator for the Offset value so only float numbers can be entered.
|
||||||
|
- in App added a shortcut key 'T' that popup a windows allowing to enter a new Tool with set diameter only when the Tool tab is on focus and only if a NCC Tool or Paint Area Tool object is installed in the Tool Tab
|
||||||
|
- if trying to add a tool using shortcut key 'T' with value zero the app will react with a message telling to use a non-zero value.
|
||||||
|
|
||||||
|
7.02.2019
|
||||||
|
|
||||||
|
- in Paint Tool, when painting single polygon, when clicking on canvas for the polygon there is no longer a selection of the entire object
|
||||||
|
- commented some debug messages
|
||||||
|
- imported speedups for shapely
|
||||||
|
- added a disable menu entry in the canvas contextual menu
|
||||||
|
- small changes in Tools layout
|
||||||
|
- added some new icons in the help menu and reorganized this menu
|
||||||
|
- added a new function and the shortcut 'leftquote' (left of Key 1) for toggle of the notebook section
|
||||||
|
- changed the Shortcut list shortcut key to F3
|
||||||
|
- moved some graphical classes out of Tool Shell to GUIElements.py where they belong
|
||||||
|
- when selecting an object on canvas by single click, it's name is displayed in status bar. When nothing is selected a blank message (nothing) it's displayed
|
||||||
|
- in Move Tool I've added the type of object that was moved in the status bar message
|
||||||
|
- color coded the status bar bullet to blue for selection
|
||||||
|
- the name of the selected objects are displayed in the status bar color coded: green for Gerber objects, Brown for Excellon, Red for Geometry and Blue for CNCJobs.
|
||||||
|
|
||||||
6.02.2019
|
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
468
camlib.py
|
@ -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 == '':
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 313 B |
Binary file not shown.
After Width: | Height: | Size: 204 B |
Binary file not shown.
After Width: | Height: | Size: 637 B |
|
@ -1,179 +0,0 @@
|
||||||
from ObjectCollection import *
|
|
||||||
from tclCommands.TclCommand import TclCommand
|
|
||||||
|
|
||||||
|
|
||||||
class TclCommandCutoutAny(TclCommand):
|
|
||||||
"""
|
|
||||||
Tcl shell command to create a board cutout geometry. Allow cutout for any shape.
|
|
||||||
|
|
||||||
example:
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# List of all command aliases, to be able use old
|
|
||||||
# names for backward compatibility (add_poly, add_polygon)
|
|
||||||
aliases = ['cutout_any', 'cut_any']
|
|
||||||
|
|
||||||
# Dictionary of types from Tcl command, needs to be ordered
|
|
||||||
arg_names = collections.OrderedDict([
|
|
||||||
('name', str),
|
|
||||||
])
|
|
||||||
|
|
||||||
# Dictionary of types from Tcl command, needs to be ordered,
|
|
||||||
# this is for options like -optionname value
|
|
||||||
option_types = collections.OrderedDict([
|
|
||||||
('dia', float),
|
|
||||||
('margin', float),
|
|
||||||
('gapsize', float),
|
|
||||||
('gaps', str)
|
|
||||||
])
|
|
||||||
|
|
||||||
# array of mandatory options for current Tcl command: required = {'name','outname'}
|
|
||||||
required = ['name']
|
|
||||||
|
|
||||||
# structured help for current command, args needs to be ordered
|
|
||||||
help = {
|
|
||||||
'main': 'Creates board cutout from an object (Gerber or Geometry) of any shape',
|
|
||||||
'args': collections.OrderedDict([
|
|
||||||
('name', 'Name of the object.'),
|
|
||||||
('dia', 'Tool diameter.'),
|
|
||||||
('margin', 'Margin over bounds.'),
|
|
||||||
('gapsize', 'size of gap.'),
|
|
||||||
('gaps', "type of gaps. Can be: 'tb' = top-bottom, 'lr' = left-right, '2tb' = 2top-2bottom, "
|
|
||||||
"'2lr' = 2left-2right, '4' = 4 cuts, '8' = 8 cuts")
|
|
||||||
]),
|
|
||||||
'examples': []
|
|
||||||
}
|
|
||||||
|
|
||||||
def execute(self, args, unnamed_args):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:param args:
|
|
||||||
:param unnamed_args:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
def subtract_rectangle(obj_, x0, y0, x1, y1):
|
|
||||||
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
|
|
||||||
obj_.subtract_polygon(pts)
|
|
||||||
|
|
||||||
if 'name' in args:
|
|
||||||
name = args['name']
|
|
||||||
else:
|
|
||||||
self.app.inform.emit(
|
|
||||||
"[WARNING]The name of the object for which cutout is done is missing. Add it and retry.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if 'margin' in args:
|
|
||||||
margin = args['margin']
|
|
||||||
else:
|
|
||||||
margin = 0.001
|
|
||||||
|
|
||||||
if 'dia' in args:
|
|
||||||
dia = args['dia']
|
|
||||||
else:
|
|
||||||
dia = 0.1
|
|
||||||
|
|
||||||
if 'gaps' in args:
|
|
||||||
gaps = args['gaps']
|
|
||||||
else:
|
|
||||||
gaps = 4
|
|
||||||
|
|
||||||
if 'gapsize' in args:
|
|
||||||
gapsize = args['gapsize']
|
|
||||||
else:
|
|
||||||
gapsize = 0.1
|
|
||||||
|
|
||||||
# Get source object.
|
|
||||||
try:
|
|
||||||
cutout_obj = self.app.collection.get_by_name(str(name))
|
|
||||||
except:
|
|
||||||
return "Could not retrieve object: %s" % name
|
|
||||||
|
|
||||||
if 0 in {dia}:
|
|
||||||
self.app.inform.emit("[WARNING]Tool Diameter is zero value. Change it to a positive integer.")
|
|
||||||
return "Tool Diameter is zero value. Change it to a positive integer."
|
|
||||||
|
|
||||||
if gaps not in ['lr', 'tb', '2lr', '2tb', 4, 8]:
|
|
||||||
self.app.inform.emit("[WARNING]Gaps value can be only one of: 'lr', 'tb', '2lr', '2tb', 4 or 8. "
|
|
||||||
"Fill in a correct value and retry. ")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get min and max data for each object as we just cut rectangles across X or Y
|
|
||||||
xmin, ymin, xmax, ymax = cutout_obj.bounds()
|
|
||||||
px = 0.5 * (xmin + xmax) + margin
|
|
||||||
py = 0.5 * (ymin + ymax) + margin
|
|
||||||
lenghtx = (xmax - xmin) + (margin * 2)
|
|
||||||
lenghty = (ymax - ymin) + (margin * 2)
|
|
||||||
|
|
||||||
gapsize = gapsize + (dia / 2)
|
|
||||||
|
|
||||||
if isinstance(cutout_obj, FlatCAMGeometry):
|
|
||||||
# rename the obj name so it can be identified as cutout
|
|
||||||
cutout_obj.options["name"] += "_cutout"
|
|
||||||
elif isinstance(cutout_obj, FlatCAMGerber):
|
|
||||||
cutout_obj.isolate(dia=dia, passes=1, overlap=1, combine=False, outname="_temp")
|
|
||||||
ext_obj = self.app.collection.get_by_name("_temp")
|
|
||||||
|
|
||||||
def geo_init(geo_obj, app_obj):
|
|
||||||
geo_obj.solid_geometry = obj_exteriors
|
|
||||||
|
|
||||||
outname = cutout_obj.options["name"] + "_cutout"
|
|
||||||
|
|
||||||
obj_exteriors = ext_obj.get_exteriors()
|
|
||||||
self.app.new_object('geometry', outname, geo_init)
|
|
||||||
|
|
||||||
self.app.collection.set_all_inactive()
|
|
||||||
self.app.collection.set_active("_temp")
|
|
||||||
self.app.on_delete()
|
|
||||||
|
|
||||||
cutout_obj = self.app.collection.get_by_name(outname)
|
|
||||||
else:
|
|
||||||
self.app.inform.emit("[ERROR]Cancelled. Object type is not supported.")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
gaps_u = int(gaps)
|
|
||||||
except ValueError:
|
|
||||||
gaps_u = gaps
|
|
||||||
|
|
||||||
if gaps_u == 8 or gaps_u == '2lr':
|
|
||||||
subtract_rectangle(cutout_obj,
|
|
||||||
xmin - gapsize, # botleft_x
|
|
||||||
py - gapsize + lenghty / 4, # botleft_y
|
|
||||||
xmax + gapsize, # topright_x
|
|
||||||
py + gapsize + lenghty / 4) # topright_y
|
|
||||||
subtract_rectangle(cutout_obj,
|
|
||||||
xmin - gapsize,
|
|
||||||
py - gapsize - lenghty / 4,
|
|
||||||
xmax + gapsize,
|
|
||||||
py + gapsize - lenghty / 4)
|
|
||||||
|
|
||||||
if gaps_u == 8 or gaps_u == '2tb':
|
|
||||||
subtract_rectangle(cutout_obj,
|
|
||||||
px - gapsize + lenghtx / 4,
|
|
||||||
ymin - gapsize,
|
|
||||||
px + gapsize + lenghtx / 4,
|
|
||||||
ymax + gapsize)
|
|
||||||
subtract_rectangle(cutout_obj,
|
|
||||||
px - gapsize - lenghtx / 4,
|
|
||||||
ymin - gapsize,
|
|
||||||
px + gapsize - lenghtx / 4,
|
|
||||||
ymax + gapsize)
|
|
||||||
|
|
||||||
if gaps_u == 4 or gaps_u == 'lr':
|
|
||||||
subtract_rectangle(cutout_obj,
|
|
||||||
xmin - gapsize,
|
|
||||||
py - gapsize,
|
|
||||||
xmax + gapsize,
|
|
||||||
py + gapsize)
|
|
||||||
|
|
||||||
if gaps_u == 4 or gaps_u == 'tb':
|
|
||||||
subtract_rectangle(cutout_obj,
|
|
||||||
px - gapsize,
|
|
||||||
ymin - gapsize,
|
|
||||||
px + gapsize,
|
|
||||||
ymax + gapsize)
|
|
||||||
|
|
||||||
cutout_obj.plot()
|
|
||||||
self.app.inform.emit("[success]Any-form Cutout operation finished.")
|
|
|
@ -1,23 +1,26 @@
|
||||||
from ObjectCollection import *
|
from 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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue