commit
c9af9008fe
296
FlatCAMApp.py
296
FlatCAMApp.py
|
@ -141,7 +141,7 @@ class App(QtCore.QObject):
|
||||||
# ################## Version and VERSION DATE ##############################
|
# ################## Version and VERSION DATE ##############################
|
||||||
# ##########################################################################
|
# ##########################################################################
|
||||||
version = 8.992
|
version = 8.992
|
||||||
version_date = "2020/01/02"
|
version_date = "2020/01/20"
|
||||||
beta = True
|
beta = True
|
||||||
engine = '3D'
|
engine = '3D'
|
||||||
|
|
||||||
|
@ -240,6 +240,9 @@ class App(QtCore.QObject):
|
||||||
# signal emitted when jumping
|
# signal emitted when jumping
|
||||||
jump_signal = pyqtSignal(tuple)
|
jump_signal = pyqtSignal(tuple)
|
||||||
|
|
||||||
|
# signal emitted when jumping
|
||||||
|
locate_signal = pyqtSignal(tuple, str)
|
||||||
|
|
||||||
# close app signal
|
# close app signal
|
||||||
close_app_signal = pyqtSignal()
|
close_app_signal = pyqtSignal()
|
||||||
|
|
||||||
|
@ -429,6 +432,7 @@ class App(QtCore.QObject):
|
||||||
"global_stats": dict(),
|
"global_stats": dict(),
|
||||||
"global_tabs_detachable": True,
|
"global_tabs_detachable": True,
|
||||||
"global_jump_ref": 'abs',
|
"global_jump_ref": 'abs',
|
||||||
|
"global_locate_pt": 'bl',
|
||||||
"global_tpdf_tmargin": 15.0,
|
"global_tpdf_tmargin": 15.0,
|
||||||
"global_tpdf_bmargin": 10.0,
|
"global_tpdf_bmargin": 10.0,
|
||||||
"global_tpdf_lmargin": 20.0,
|
"global_tpdf_lmargin": 20.0,
|
||||||
|
@ -524,8 +528,8 @@ class App(QtCore.QObject):
|
||||||
"global_cursor_type": "small",
|
"global_cursor_type": "small",
|
||||||
"global_cursor_size": 20,
|
"global_cursor_size": 20,
|
||||||
"global_cursor_width": 2,
|
"global_cursor_width": 2,
|
||||||
"global_cursor_color": '#000000',
|
"global_cursor_color": '#FF0000',
|
||||||
"global_cursor_color_enabled": False,
|
"global_cursor_color_enabled": True,
|
||||||
|
|
||||||
# Gerber General
|
# Gerber General
|
||||||
"gerber_plot": True,
|
"gerber_plot": True,
|
||||||
|
@ -954,6 +958,24 @@ class App(QtCore.QObject):
|
||||||
"tools_cal_toolchange_xy": '',
|
"tools_cal_toolchange_xy": '',
|
||||||
"tools_cal_sec_point": 'tl',
|
"tools_cal_sec_point": 'tl',
|
||||||
|
|
||||||
|
# Drills Extraction Tool
|
||||||
|
"tools_edrills_hole_type": 'fixed',
|
||||||
|
"tools_edrills_hole_fixed_dia": 0.5,
|
||||||
|
"tools_edrills_hole_prop_factor": 80.0,
|
||||||
|
"tools_edrills_circular_ring": 0.2,
|
||||||
|
"tools_edrills_oblong_ring": 0.2,
|
||||||
|
"tools_edrills_square_ring": 0.2,
|
||||||
|
"tools_edrills_rectangular_ring": 0.2,
|
||||||
|
"tools_edrills_others_ring": 0.2,
|
||||||
|
"tools_edrills_circular": True,
|
||||||
|
"tools_edrills_oblong": False,
|
||||||
|
"tools_edrills_square": False,
|
||||||
|
"tools_edrills_rectangular": False,
|
||||||
|
"tools_edrills_others": False,
|
||||||
|
|
||||||
|
# Align Objects Tool
|
||||||
|
"tools_align_objects_align_type": 'sp',
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
# file associations
|
# file associations
|
||||||
"fa_excellon": 'drd, drl, exc, ncd, tap, xln',
|
"fa_excellon": 'drd, drl, exc, ncd, tap, xln',
|
||||||
|
@ -1578,6 +1600,21 @@ class App(QtCore.QObject):
|
||||||
"tools_cal_toolchange_xy": self.ui.tools2_defaults_form.tools2_cal_group.toolchange_xy_entry,
|
"tools_cal_toolchange_xy": self.ui.tools2_defaults_form.tools2_cal_group.toolchange_xy_entry,
|
||||||
"tools_cal_sec_point": self.ui.tools2_defaults_form.tools2_cal_group.second_point_radio,
|
"tools_cal_sec_point": self.ui.tools2_defaults_form.tools2_cal_group.second_point_radio,
|
||||||
|
|
||||||
|
# Extract Drills Tool
|
||||||
|
"tools_edrills_hole_type": self.ui.tools2_defaults_form.tools2_edrills_group.hole_size_radio,
|
||||||
|
"tools_edrills_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_edrills_group.dia_entry,
|
||||||
|
"tools_edrills_hole_prop_factor": self.ui.tools2_defaults_form.tools2_edrills_group.factor_entry,
|
||||||
|
"tools_edrills_circular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.circular_ring_entry,
|
||||||
|
"tools_edrills_oblong_ring": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_ring_entry,
|
||||||
|
"tools_edrills_square_ring": self.ui.tools2_defaults_form.tools2_edrills_group.square_ring_entry,
|
||||||
|
"tools_edrills_rectangular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_ring_entry,
|
||||||
|
"tools_edrills_others_ring": self.ui.tools2_defaults_form.tools2_edrills_group.other_ring_entry,
|
||||||
|
"tools_edrills_circular": self.ui.tools2_defaults_form.tools2_edrills_group.circular_cb,
|
||||||
|
"tools_edrills_oblong": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_cb,
|
||||||
|
"tools_edrills_square": self.ui.tools2_defaults_form.tools2_edrills_group.square_cb,
|
||||||
|
"tools_edrills_rectangular": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_cb,
|
||||||
|
"tools_edrills_others": self.ui.tools2_defaults_form.tools2_edrills_group.other_cb,
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
# File associations
|
# File associations
|
||||||
"fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text,
|
"fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text,
|
||||||
|
@ -1923,6 +1960,7 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
self.ui.menueditorigin.triggered.connect(self.on_set_origin)
|
self.ui.menueditorigin.triggered.connect(self.on_set_origin)
|
||||||
self.ui.menueditjump.triggered.connect(self.on_jump_to)
|
self.ui.menueditjump.triggered.connect(self.on_jump_to)
|
||||||
|
self.ui.menueditlocate.triggered.connect(lambda: self.on_locate(obj=self.collection.get_active()))
|
||||||
|
|
||||||
self.ui.menuedittoggleunits.triggered.connect(self.on_toggle_units_click)
|
self.ui.menuedittoggleunits.triggered.connect(self.on_toggle_units_click)
|
||||||
self.ui.menueditselectall.triggered.connect(self.on_selectall)
|
self.ui.menueditselectall.triggered.connect(self.on_selectall)
|
||||||
|
@ -2464,12 +2502,14 @@ class App(QtCore.QObject):
|
||||||
self.qrcode_tool = None
|
self.qrcode_tool = None
|
||||||
self.copper_thieving_tool = None
|
self.copper_thieving_tool = None
|
||||||
self.fiducial_tool = None
|
self.fiducial_tool = None
|
||||||
|
self.edrills_tool = None
|
||||||
|
self.align_objects_tool = None
|
||||||
|
|
||||||
# always install tools only after the shell is initialized because the self.inform.emit() depends on shell
|
# always install tools only after the shell is initialized because the self.inform.emit() depends on shell
|
||||||
try:
|
try:
|
||||||
self.install_tools()
|
self.install_tools()
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
pass
|
log.debug("App.__init__() install tools() --> %s" % str(e))
|
||||||
|
|
||||||
# ##################################################################################
|
# ##################################################################################
|
||||||
# ########################### SETUP RECENT ITEMS ###################################
|
# ########################### SETUP RECENT ITEMS ###################################
|
||||||
|
@ -3017,13 +3057,6 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self.dblsidedtool = DblSidedTool(self)
|
|
||||||
self.dblsidedtool.install(icon=QtGui.QIcon(self.resource_location + '/doubleside16.png'), separator=True)
|
|
||||||
|
|
||||||
self.cal_exc_tool = ToolCalibration(self)
|
|
||||||
self.cal_exc_tool.install(icon=QtGui.QIcon(self.resource_location + '/calibrate_16.png'), pos=self.ui.menutool,
|
|
||||||
before=self.dblsidedtool.menuAction,
|
|
||||||
separator=False)
|
|
||||||
self.distance_tool = Distance(self)
|
self.distance_tool = Distance(self)
|
||||||
self.distance_tool.install(icon=QtGui.QIcon(self.resource_location + '/distance16.png'), pos=self.ui.menuedit,
|
self.distance_tool.install(icon=QtGui.QIcon(self.resource_location + '/distance16.png'), pos=self.ui.menuedit,
|
||||||
before=self.ui.menueditorigin,
|
before=self.ui.menueditorigin,
|
||||||
|
@ -3035,6 +3068,20 @@ class App(QtCore.QObject):
|
||||||
before=self.ui.menueditorigin,
|
before=self.ui.menueditorigin,
|
||||||
separator=True)
|
separator=True)
|
||||||
|
|
||||||
|
self.dblsidedtool = DblSidedTool(self)
|
||||||
|
self.dblsidedtool.install(icon=QtGui.QIcon(self.resource_location + '/doubleside16.png'), separator=False)
|
||||||
|
|
||||||
|
self.cal_exc_tool = ToolCalibration(self)
|
||||||
|
self.cal_exc_tool.install(icon=QtGui.QIcon(self.resource_location + '/calibrate_16.png'), pos=self.ui.menutool,
|
||||||
|
before=self.dblsidedtool.menuAction,
|
||||||
|
separator=False)
|
||||||
|
|
||||||
|
self.align_objects_tool = AlignObjects(self)
|
||||||
|
self.align_objects_tool.install(icon=QtGui.QIcon(self.resource_location + '/align16.png'), separator=False)
|
||||||
|
|
||||||
|
self.edrills_tool = ToolExtractDrills(self)
|
||||||
|
self.edrills_tool.install(icon=QtGui.QIcon(self.resource_location + '/drill16.png'), separator=True)
|
||||||
|
|
||||||
self.panelize_tool = Panelize(self)
|
self.panelize_tool = Panelize(self)
|
||||||
self.panelize_tool.install(icon=QtGui.QIcon(self.resource_location + '/panelize16.png'))
|
self.panelize_tool.install(icon=QtGui.QIcon(self.resource_location + '/panelize16.png'))
|
||||||
|
|
||||||
|
@ -3199,6 +3246,7 @@ class App(QtCore.QObject):
|
||||||
self.ui.distance_min_btn.triggered.connect(lambda: self.distance_min_tool.run(toggle=True))
|
self.ui.distance_min_btn.triggered.connect(lambda: self.distance_min_tool.run(toggle=True))
|
||||||
self.ui.origin_btn.triggered.connect(self.on_set_origin)
|
self.ui.origin_btn.triggered.connect(self.on_set_origin)
|
||||||
self.ui.jmp_btn.triggered.connect(self.on_jump_to)
|
self.ui.jmp_btn.triggered.connect(self.on_jump_to)
|
||||||
|
self.ui.locate_btn.triggered.connect(lambda: self.on_locate(obj=self.collection.get_active()))
|
||||||
|
|
||||||
self.ui.shell_btn.triggered.connect(self.on_toggle_shell)
|
self.ui.shell_btn.triggered.connect(self.on_toggle_shell)
|
||||||
self.ui.new_script_btn.triggered.connect(self.on_filenewscript)
|
self.ui.new_script_btn.triggered.connect(self.on_filenewscript)
|
||||||
|
@ -3208,6 +3256,9 @@ class App(QtCore.QObject):
|
||||||
# Tools Toolbar Signals
|
# Tools Toolbar Signals
|
||||||
self.ui.dblsided_btn.triggered.connect(lambda: self.dblsidedtool.run(toggle=True))
|
self.ui.dblsided_btn.triggered.connect(lambda: self.dblsidedtool.run(toggle=True))
|
||||||
self.ui.cal_btn.triggered.connect(lambda: self.cal_exc_tool.run(toggle=True))
|
self.ui.cal_btn.triggered.connect(lambda: self.cal_exc_tool.run(toggle=True))
|
||||||
|
self.ui.align_btn.triggered.connect(lambda: self.align_objects_tool.run(toggle=True))
|
||||||
|
self.ui.extract_btn.triggered.connect(lambda: self.edrills_tool.run(toggle=True))
|
||||||
|
|
||||||
self.ui.cutout_btn.triggered.connect(lambda: self.cutout_tool.run(toggle=True))
|
self.ui.cutout_btn.triggered.connect(lambda: self.cutout_tool.run(toggle=True))
|
||||||
self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True))
|
self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True))
|
||||||
self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True))
|
self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True))
|
||||||
|
@ -4237,9 +4288,20 @@ class App(QtCore.QObject):
|
||||||
obj.options['xmax'] = xmax
|
obj.options['xmax'] = xmax
|
||||||
obj.options['ymax'] = ymax
|
obj.options['ymax'] = ymax
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning("The object has no bounds properties. %s" % str(e))
|
log.warning("App.new_object() -> The object has no bounds properties. %s" % str(e))
|
||||||
return "fail"
|
return "fail"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if kind == 'excellon':
|
||||||
|
obj.fill_color = self.app.defaults["excellon_plot_fill"]
|
||||||
|
obj.outline_color = self.app.defaults["excellon_plot_line"]
|
||||||
|
|
||||||
|
if kind == 'gerber':
|
||||||
|
obj.fill_color = self.app.defaults["gerber_plot_fill"]
|
||||||
|
obj.outline_color = self.app.defaults["gerber_plot_line"]
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("App.new_object() -> setting colors error. %s" % str(e))
|
||||||
|
|
||||||
# update the KeyWords list with the name of the file
|
# update the KeyWords list with the name of the file
|
||||||
self.myKeywords.append(obj.options['name'])
|
self.myKeywords.append(obj.options['name'])
|
||||||
|
|
||||||
|
@ -7140,15 +7202,13 @@ class App(QtCore.QObject):
|
||||||
obj.options['ymin'] = b
|
obj.options['ymin'] = b
|
||||||
obj.options['xmax'] = c
|
obj.options['xmax'] = c
|
||||||
obj.options['ymax'] = d
|
obj.options['ymax'] = d
|
||||||
self.inform.emit('[success] %s...' %
|
self.inform.emit('[success] %s...' % _('Origin set'))
|
||||||
_('Origin set'))
|
|
||||||
if noplot_sig is False:
|
if noplot_sig is False:
|
||||||
self.replot_signal.emit([])
|
self.replot_signal.emit([])
|
||||||
|
|
||||||
if location is not None:
|
if location is not None:
|
||||||
if len(location) != 2:
|
if len(location) != 2:
|
||||||
self.inform.emit('[ERROR_NOTCL] %s...' %
|
self.inform.emit('[ERROR_NOTCL] %s...' % _("Origin coordinates specified but incomplete."))
|
||||||
_("Origin coordinates specified but incomplete."))
|
|
||||||
return 'fail'
|
return 'fail'
|
||||||
|
|
||||||
x, y = location
|
x, y = location
|
||||||
|
@ -7235,7 +7295,151 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
self.jump_signal.emit(location)
|
self.jump_signal.emit(location)
|
||||||
|
|
||||||
units = self.defaults['units'].upper()
|
if fit_center:
|
||||||
|
self.plotcanvas.fit_center(loc=location)
|
||||||
|
|
||||||
|
cursor = QtGui.QCursor()
|
||||||
|
|
||||||
|
if self.is_legacy is False:
|
||||||
|
# I don't know where those differences come from but they are constant for the current
|
||||||
|
# execution of the application and they are multiples of a value around 0.0263mm.
|
||||||
|
# In a random way sometimes they are more sometimes they are less
|
||||||
|
# if units == 'MM':
|
||||||
|
# cal_factor = 0.0263
|
||||||
|
# else:
|
||||||
|
# cal_factor = 0.0263 / 25.4
|
||||||
|
|
||||||
|
cal_location = (location[0], location[1])
|
||||||
|
|
||||||
|
canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
|
jump_loc = self.plotcanvas.translate_coords_2((cal_location[0], cal_location[1]))
|
||||||
|
|
||||||
|
j_pos = (
|
||||||
|
int(canvas_origin.x() + round(jump_loc[0])),
|
||||||
|
int(canvas_origin.y() + round(jump_loc[1]))
|
||||||
|
)
|
||||||
|
cursor.setPos(j_pos[0], j_pos[1])
|
||||||
|
else:
|
||||||
|
# find the canvas origin which is in the top left corner
|
||||||
|
canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
|
# determine the coordinates for the lowest left point of the canvas
|
||||||
|
x0, y0 = canvas_origin.x(), canvas_origin.y() + self.ui.right_layout.geometry().height()
|
||||||
|
|
||||||
|
# transform the given location from data coordinates to display coordinates. THe display coordinates are
|
||||||
|
# in pixels where the origin 0,0 is in the lowest left point of the display window (in our case is the
|
||||||
|
# canvas) and the point (width, height) is in the top-right location
|
||||||
|
loc = self.plotcanvas.axes.transData.transform_point(location)
|
||||||
|
j_pos = (
|
||||||
|
int(x0 + loc[0]),
|
||||||
|
int(y0 - loc[1])
|
||||||
|
)
|
||||||
|
cursor.setPos(j_pos[0], j_pos[1])
|
||||||
|
self.plotcanvas.mouse = [location[0], location[1]]
|
||||||
|
if self.defaults["global_cursor_color_enabled"] is True:
|
||||||
|
self.plotcanvas.draw_cursor(x_pos=location[0], y_pos=location[1], color=self.cursor_color_3D)
|
||||||
|
else:
|
||||||
|
self.plotcanvas.draw_cursor(x_pos=location[0], y_pos=location[1])
|
||||||
|
|
||||||
|
if self.grid_status():
|
||||||
|
# Update cursor
|
||||||
|
self.app_cursor.set_data(np.asarray([(location[0], location[1])]),
|
||||||
|
symbol='++', edge_color=self.cursor_color_3D,
|
||||||
|
edge_width=self.defaults["global_cursor_width"],
|
||||||
|
size=self.defaults["global_cursor_size"])
|
||||||
|
|
||||||
|
# Set the position label
|
||||||
|
self.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||||
|
"<b>Y</b>: %.4f" % (location[0], location[1]))
|
||||||
|
# Set the relative position label
|
||||||
|
dx = location[0] - float(self.rel_point1[0])
|
||||||
|
dy = location[1] - float(self.rel_point1[1])
|
||||||
|
self.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||||
|
"%.4f " % (dx, dy))
|
||||||
|
|
||||||
|
self.inform.emit('[success] %s' % _("Done."))
|
||||||
|
return location
|
||||||
|
|
||||||
|
def on_locate(self, obj, fit_center=True):
|
||||||
|
"""
|
||||||
|
Jump to one of the corners (or center) of an object by setting the mouse cursor location
|
||||||
|
:return:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.report_usage("on_locate()")
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
self.inform.emit('[WARNING_NOTCL] %s' % _("There is no object selected..."))
|
||||||
|
return 'fail'
|
||||||
|
|
||||||
|
class DialogBoxChoice(QtWidgets.QDialog):
|
||||||
|
def __init__(self, title=None, icon=None, choice='bl'):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param title: string with the window title
|
||||||
|
"""
|
||||||
|
super(DialogBoxChoice, self).__init__()
|
||||||
|
|
||||||
|
self.ok = False
|
||||||
|
|
||||||
|
self.setWindowIcon(icon)
|
||||||
|
self.setWindowTitle(str(title))
|
||||||
|
|
||||||
|
self.form = QtWidgets.QFormLayout(self)
|
||||||
|
|
||||||
|
self.ref_radio = RadioSet([
|
||||||
|
{"label": _("Bottom-Left"), "value": "bl"},
|
||||||
|
{"label": _("Top-Left"), "value": "tl"},
|
||||||
|
{"label": _("Bottom-Right"), "value": "br"},
|
||||||
|
{"label": _("Top-Right"), "value": "tr"},
|
||||||
|
{"label": _("Center"), "value": "c"}
|
||||||
|
], orientation='vertical', stretch=False)
|
||||||
|
self.ref_radio.set_value(choice)
|
||||||
|
self.form.addRow(self.ref_radio)
|
||||||
|
|
||||||
|
self.button_box = QtWidgets.QDialogButtonBox(
|
||||||
|
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
|
||||||
|
Qt.Horizontal, parent=self)
|
||||||
|
self.form.addRow(self.button_box)
|
||||||
|
|
||||||
|
self.button_box.accepted.connect(self.accept)
|
||||||
|
self.button_box.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
if self.exec_() == QtWidgets.QDialog.Accepted:
|
||||||
|
self.ok = True
|
||||||
|
self.location_point = self.ref_radio.get_value()
|
||||||
|
else:
|
||||||
|
self.ok = False
|
||||||
|
self.location_point = None
|
||||||
|
|
||||||
|
dia_box = DialogBoxChoice(title=_("Locate ..."),
|
||||||
|
icon=QtGui.QIcon(self.resource_location + '/locate16.png'),
|
||||||
|
choice=self.defaults['global_locate_pt'])
|
||||||
|
|
||||||
|
if dia_box.ok is True:
|
||||||
|
try:
|
||||||
|
location_point = dia_box.location_point
|
||||||
|
self.defaults['global_locate_pt'] = dia_box.location_point
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
loc_b = obj.bounds()
|
||||||
|
if location_point == 'bl':
|
||||||
|
location = (loc_b[0], loc_b[1])
|
||||||
|
elif location_point == 'tl':
|
||||||
|
location = (loc_b[0], loc_b[3])
|
||||||
|
elif location_point == 'br':
|
||||||
|
location = (loc_b[2], loc_b[1])
|
||||||
|
elif location_point == 'tr':
|
||||||
|
location = (loc_b[2], loc_b[3])
|
||||||
|
else:
|
||||||
|
# center
|
||||||
|
cx = loc_b[0] + ((loc_b[2] - loc_b[0]) / 2)
|
||||||
|
cy = loc_b[1] + ((loc_b[3] - loc_b[1]) / 2)
|
||||||
|
location = (cx, cy)
|
||||||
|
|
||||||
|
self.locate_signal.emit(location, location_point)
|
||||||
|
|
||||||
if fit_center:
|
if fit_center:
|
||||||
self.plotcanvas.fit_center(loc=location)
|
self.plotcanvas.fit_center(loc=location)
|
||||||
|
@ -8465,7 +8669,7 @@ class App(QtCore.QObject):
|
||||||
self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
|
self.draw_moving_selection_shape(self.pos, pos, color=self.defaults['global_alt_sel_line'],
|
||||||
face_color=self.defaults['global_alt_sel_fill'])
|
face_color=self.defaults['global_alt_sel_fill'])
|
||||||
self.selection_type = False
|
self.selection_type = False
|
||||||
elif dx > 0:
|
elif dx >= 0:
|
||||||
self.draw_moving_selection_shape(self.pos, pos)
|
self.draw_moving_selection_shape(self.pos, pos)
|
||||||
self.selection_type = True
|
self.selection_type = True
|
||||||
else:
|
else:
|
||||||
|
@ -8862,6 +9066,7 @@ class App(QtCore.QObject):
|
||||||
pt4 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymax']))
|
pt4 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymax']))
|
||||||
|
|
||||||
sel_rect = Polygon([pt1, pt2, pt3, pt4])
|
sel_rect = Polygon([pt1, pt2, pt3, pt4])
|
||||||
|
|
||||||
if self.defaults['units'].upper() == 'MM':
|
if self.defaults['units'].upper() == 'MM':
|
||||||
sel_rect = sel_rect.buffer(-0.1)
|
sel_rect = sel_rect.buffer(-0.1)
|
||||||
sel_rect = sel_rect.buffer(0.2)
|
sel_rect = sel_rect.buffer(0.2)
|
||||||
|
@ -10378,7 +10583,8 @@ class App(QtCore.QObject):
|
||||||
self.report_usage("export_svg()")
|
self.report_usage("export_svg()")
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.defaults["global_last_save_folder"]
|
filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \
|
||||||
|
is not None else self.defaults["global_last_folder"]
|
||||||
|
|
||||||
self.log.debug("export_svg()")
|
self.log.debug("export_svg()")
|
||||||
|
|
||||||
|
@ -10446,7 +10652,8 @@ class App(QtCore.QObject):
|
||||||
self.report_usage("save source file()")
|
self.report_usage("save source file()")
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.defaults["global_last_save_folder"]
|
filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \
|
||||||
|
is not None else self.defaults["global_last_folder"]
|
||||||
|
|
||||||
self.log.debug("save source file()")
|
self.log.debug("save source file()")
|
||||||
|
|
||||||
|
@ -10489,7 +10696,10 @@ class App(QtCore.QObject):
|
||||||
self.report_usage("export_excellon()")
|
self.report_usage("export_excellon()")
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.defaults["global_last_save_folder"] + '/' + 'exported_excellon'
|
if self.defaults["global_last_save_folder"]:
|
||||||
|
filename = self.defaults["global_last_save_folder"] + '/' + 'exported_excellon'
|
||||||
|
else:
|
||||||
|
filename = self.defaults["global_last_folder"] + '/' + 'exported_excellon'
|
||||||
|
|
||||||
self.log.debug("export_excellon()")
|
self.log.debug("export_excellon()")
|
||||||
|
|
||||||
|
@ -10645,7 +10855,8 @@ class App(QtCore.QObject):
|
||||||
self.report_usage("export_gerber()")
|
self.report_usage("export_gerber()")
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.defaults["global_last_save_folder"]
|
filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \
|
||||||
|
is not None else self.defaults["global_last_folder"]
|
||||||
|
|
||||||
self.log.debug("export_gerber()")
|
self.log.debug("export_gerber()")
|
||||||
|
|
||||||
|
@ -10781,7 +10992,8 @@ class App(QtCore.QObject):
|
||||||
self.report_usage("export_dxf()")
|
self.report_usage("export_dxf()")
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.defaults["global_last_save_folder"]
|
filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \
|
||||||
|
is not None else self.defaults["global_last_folder"]
|
||||||
|
|
||||||
self.log.debug("export_dxf()")
|
self.log.debug("export_dxf()")
|
||||||
|
|
||||||
|
@ -11983,8 +12195,13 @@ class App(QtCore.QObject):
|
||||||
plot_container = container
|
plot_container = container
|
||||||
else:
|
else:
|
||||||
plot_container = self.ui.right_layout
|
plot_container = self.ui.right_layout
|
||||||
print("step_1")
|
|
||||||
if self.is_legacy is False:
|
modifier = QtWidgets.QApplication.queryKeyboardModifiers()
|
||||||
|
if self.is_legacy is True or modifier == QtCore.Qt.ControlModifier:
|
||||||
|
self.is_legacy = True
|
||||||
|
self.defaults["global_graphic_engine"] = "2D"
|
||||||
|
self.plotcanvas = PlotCanvasLegacy(plot_container, self)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
self.plotcanvas = PlotCanvas(plot_container, self)
|
self.plotcanvas = PlotCanvas(plot_container, self)
|
||||||
except Exception as er:
|
except Exception as er:
|
||||||
|
@ -11997,13 +12214,9 @@ class App(QtCore.QObject):
|
||||||
msg += msg_txt
|
msg += msg_txt
|
||||||
self.inform.emit(msg)
|
self.inform.emit(msg)
|
||||||
return 'fail'
|
return 'fail'
|
||||||
else:
|
|
||||||
self.plotcanvas = PlotCanvasLegacy(plot_container, self)
|
|
||||||
print("step_2")
|
|
||||||
|
|
||||||
# So it can receive key presses
|
# So it can receive key presses
|
||||||
self.plotcanvas.native.setFocus()
|
self.plotcanvas.native.setFocus()
|
||||||
print("step_3")
|
|
||||||
|
|
||||||
self.mm = self.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot)
|
self.mm = self.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot)
|
||||||
self.mp = self.plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot)
|
self.mp = self.plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot)
|
||||||
|
@ -12012,28 +12225,22 @@ class App(QtCore.QObject):
|
||||||
|
|
||||||
# Keys over plot enabled
|
# Keys over plot enabled
|
||||||
self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
|
self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
|
||||||
print("step_4")
|
|
||||||
|
|
||||||
if self.defaults['global_cursor_type'] == 'small':
|
if self.defaults['global_cursor_type'] == 'small':
|
||||||
self.app_cursor = self.plotcanvas.new_cursor()
|
self.app_cursor = self.plotcanvas.new_cursor()
|
||||||
else:
|
else:
|
||||||
self.app_cursor = self.plotcanvas.new_cursor(big=True)
|
self.app_cursor = self.plotcanvas.new_cursor(big=True)
|
||||||
|
|
||||||
print("step_5")
|
|
||||||
|
|
||||||
if self.ui.grid_snap_btn.isChecked():
|
if self.ui.grid_snap_btn.isChecked():
|
||||||
self.app_cursor.enabled = True
|
self.app_cursor.enabled = True
|
||||||
else:
|
else:
|
||||||
self.app_cursor.enabled = False
|
self.app_cursor.enabled = False
|
||||||
|
|
||||||
print("step_6")
|
|
||||||
|
|
||||||
if self.is_legacy is False:
|
if self.is_legacy is False:
|
||||||
self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
|
self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
|
||||||
else:
|
else:
|
||||||
# will use the default Matplotlib axes
|
# will use the default Matplotlib axes
|
||||||
self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
|
self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
|
||||||
print("step_7")
|
|
||||||
|
|
||||||
def on_zoom_fit(self, event):
|
def on_zoom_fit(self, event):
|
||||||
"""
|
"""
|
||||||
|
@ -12262,19 +12469,12 @@ class App(QtCore.QObject):
|
||||||
new_line_color = color_variant(new_color[:7], 0.7)
|
new_line_color = color_variant(new_color[:7], 0.7)
|
||||||
|
|
||||||
for sel_obj in sel_obj_list:
|
for sel_obj in sel_obj_list:
|
||||||
if self.is_legacy is False:
|
sel_obj.fill_color = new_color
|
||||||
sel_obj.fill_color = new_color
|
sel_obj.outline_color = new_line_color
|
||||||
sel_obj.outline_color = new_line_color
|
|
||||||
|
|
||||||
sel_obj.shapes.redraw(
|
sel_obj.shapes.redraw(
|
||||||
update_colors=(new_color, new_line_color)
|
update_colors=(new_color, new_line_color)
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
sel_obj.fill_color = new_color
|
|
||||||
sel_obj.outline_color = new_line_color
|
|
||||||
sel_obj.shapes.redraw(
|
|
||||||
update_colors=(new_color, new_line_color)
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_grid_snap_triggered(self, state):
|
def on_grid_snap_triggered(self, state):
|
||||||
if state:
|
if state:
|
||||||
|
|
|
@ -1307,7 +1307,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
else:
|
else:
|
||||||
iso_name = outname
|
iso_name = outname
|
||||||
|
|
||||||
# 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"] = str(self.options["isotooldia"])
|
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||||
|
@ -1318,8 +1317,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
iso_offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
|
iso_offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
|
||||||
|
|
||||||
# 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
|
||||||
mill_t = 1 if milling_type == 'cl' else 0
|
mill_dir = 1 if milling_type == 'cl' else 0
|
||||||
geom = self.generate_envelope(iso_offset, mill_t, geometry=work_geo, env_iso_type=iso_t,
|
geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
|
||||||
follow=follow, nr_passes=i)
|
follow=follow, nr_passes=i)
|
||||||
|
|
||||||
if geom == 'fail':
|
if geom == 'fail':
|
||||||
|
@ -1438,7 +1437,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
else:
|
else:
|
||||||
iso_name = outname
|
iso_name = outname
|
||||||
|
|
||||||
# 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"] = str(self.options["isotooldia"])
|
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||||
|
@ -1448,9 +1446,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
||||||
geo_obj.tool_type = 'C1'
|
geo_obj.tool_type = 'C1'
|
||||||
|
|
||||||
# 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
|
||||||
mill_t = 1 if milling_type == 'cl' else 0
|
mill_dir = 1 if milling_type == 'cl' else 0
|
||||||
mill_t = 1 if milling_type == 'cl' else 0
|
geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
|
||||||
geom = self.generate_envelope(offset, mill_t, geometry=work_geo, env_iso_type=iso_t,
|
|
||||||
follow=follow,
|
follow=follow,
|
||||||
nr_passes=i)
|
nr_passes=i)
|
||||||
|
|
||||||
|
@ -2641,7 +2638,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
||||||
horizontal_header.setDefaultSectionSize(70)
|
horizontal_header.setDefaultSectionSize(70)
|
||||||
horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
|
horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
|
||||||
horizontal_header.resizeSection(0, 20)
|
horizontal_header.resizeSection(0, 20)
|
||||||
horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
if self.app.defaults["global_app_level"] == 'b':
|
||||||
|
horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
|
||||||
|
else:
|
||||||
|
horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
|
horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Stretch)
|
horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Stretch)
|
||||||
|
|
46
README.md
46
README.md
|
@ -9,19 +9,59 @@ CAD program, and create G-Code for Isolation routing.
|
||||||
|
|
||||||
=================================================
|
=================================================
|
||||||
|
|
||||||
8.01.2019
|
15.01.2020
|
||||||
|
|
||||||
|
- added key shortcuts and toolbar icons for the new tools: Align Object Tool (ALT+A) and Extract Drills (ALT+I)
|
||||||
|
- added new functionality (key shortcut SHIFT+J) to locate the corners of the bounding box (and center) in a selected object
|
||||||
|
|
||||||
|
14.01.2020
|
||||||
|
|
||||||
|
- in Extract Drill Tool added a new method of drills extraction. The methods are: fixed diameter, fixed annular ring and proportional
|
||||||
|
- in Align Objects Tool finished the Single Point method of alignment
|
||||||
|
- working on the Dual Point option in Align Objects Tool - angle has to be recalculated
|
||||||
|
- finished Dual Point option in Align Objects Tool
|
||||||
|
|
||||||
|
13.01.2020
|
||||||
|
|
||||||
|
- fixed a small GUI issue in Excellon UI when Basic mode is active
|
||||||
|
- started the add of a new Tool: Align Objects Tool which will align (sync) objects of Gerber or Excellon type
|
||||||
|
- fixed an issue in Gerber parser introduced recently due of changes made to make Gerber files produced by Sprint Layout
|
||||||
|
- working on the Align Objects Tool
|
||||||
|
|
||||||
|
12.01.2020
|
||||||
|
|
||||||
|
- improved the circle approximation resolution
|
||||||
|
- fixed an issue in Gerber parser with detecting old kind of units
|
||||||
|
- if CTRL key is pressed during app startup the app will start in the Legacy(2D) graphic engine compatibility mode
|
||||||
|
|
||||||
|
11.01.2020
|
||||||
|
|
||||||
|
- fixed an issue in the Distance Tool
|
||||||
|
- expanded the Extract Drills Tool to use a particular annular ring for each type of aperture flash (pad)
|
||||||
|
- Extract Drills Tool: fixed issue with oblong pads and with pads made from aperture macros
|
||||||
|
- Extract Drills Tool: added controls in Edit -> Preferences
|
||||||
|
|
||||||
|
10.02.2020
|
||||||
|
|
||||||
|
- working on a new tool: Extract Drills Tool who will create a Excellon object out of the apertures of a Gerber object
|
||||||
|
- finished the GUI in the Extract Drills Tool
|
||||||
|
- fixed issue in Film Tool where some parameters names in calls of method export_positive() were not matching the actual parameters name
|
||||||
|
- finished the Extract Drills Tool
|
||||||
|
- fixed a small issue in the DoubleSided Tool
|
||||||
|
|
||||||
|
8.01.2020
|
||||||
|
|
||||||
- working in NCC Tool
|
- working in NCC Tool
|
||||||
- selected rows in the Tools Tables will stay colored in blue after loosing focus instead of the default gray
|
- selected rows in the Tools Tables will stay colored in blue after loosing focus instead of the default gray
|
||||||
- in NCC Tool the Tool name in the Parameters section will be the Tool ID in the Tool Table
|
- in NCC Tool the Tool name in the Parameters section will be the Tool ID in the Tool Table
|
||||||
- added an exception catch in case the plotcanvas init failed for the OpenGL graphic engine and warn user about what happened
|
- added an exception catch in case the plotcanvas init failed for the OpenGL graphic engine and warn user about what happened
|
||||||
|
|
||||||
7.01.2019
|
7.01.2020
|
||||||
|
|
||||||
- solved issue #368 - when using the Enable/Disable prj context menu entries the plotted status is not updated in the object properties
|
- solved issue #368 - when using the Enable/Disable prj context menu entries the plotted status is not updated in the object properties
|
||||||
- updates in NCC Tool
|
- updates in NCC Tool
|
||||||
|
|
||||||
6.01.2019
|
6.01.2020
|
||||||
|
|
||||||
- working on new NCC Tool
|
- working on new NCC Tool
|
||||||
|
|
||||||
|
|
35
camlib.py
35
camlib.py
|
@ -458,8 +458,8 @@ class Geometry(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
"units": 'in',
|
"units": 'mm',
|
||||||
"geo_steps_per_circle": 64
|
# "geo_steps_per_circle": 128
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, geo_steps_per_circle=None):
|
def __init__(self, geo_steps_per_circle=None):
|
||||||
|
@ -528,13 +528,13 @@ class Geometry(object):
|
||||||
self.solid_geometry = []
|
self.solid_geometry = []
|
||||||
|
|
||||||
if type(self.solid_geometry) is list:
|
if type(self.solid_geometry) is list:
|
||||||
self.solid_geometry.append(Point(origin).buffer(
|
self.solid_geometry.append(Point(origin).buffer(radius, int(self.geo_steps_per_circle)))
|
||||||
radius, int(int(self.geo_steps_per_circle) / 4)))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.solid_geometry = self.solid_geometry.union(Point(origin).buffer(
|
self.solid_geometry = self.solid_geometry.union(
|
||||||
radius, int(int(self.geo_steps_per_circle) / 4)))
|
Point(origin).buffer(radius, int(self.geo_steps_per_circle))
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("Failed to run union on polygons. %s" % str(e))
|
log.error("Failed to run union on polygons. %s" % str(e))
|
||||||
return
|
return
|
||||||
|
@ -944,7 +944,7 @@ class Geometry(object):
|
||||||
geo_iso.append(pol)
|
geo_iso.append(pol)
|
||||||
else:
|
else:
|
||||||
corner_type = 1 if corner is None else corner
|
corner_type = 1 if corner is None else corner
|
||||||
geo_iso.append(pol.buffer(offset, int(int(self.geo_steps_per_circle) / 4), join_style=corner_type))
|
geo_iso.append(pol.buffer(offset, int(self.geo_steps_per_circle), join_style=corner_type))
|
||||||
pol_nr += 1
|
pol_nr += 1
|
||||||
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
|
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
|
||||||
|
|
||||||
|
@ -959,8 +959,7 @@ class Geometry(object):
|
||||||
geo_iso.append(working_geo)
|
geo_iso.append(working_geo)
|
||||||
else:
|
else:
|
||||||
corner_type = 1 if corner is None else corner
|
corner_type = 1 if corner is None else corner
|
||||||
geo_iso.append(working_geo.buffer(offset, int(int(self.geo_steps_per_circle) / 4),
|
geo_iso.append(working_geo.buffer(offset, int(self.geo_steps_per_circle), join_style=corner_type))
|
||||||
join_style=corner_type))
|
|
||||||
|
|
||||||
self.app.proc_container.update_view_text(' %s' % _("Buffering"))
|
self.app.proc_container.update_view_text(' %s' % _("Buffering"))
|
||||||
geo_iso = unary_union(geo_iso)
|
geo_iso = unary_union(geo_iso)
|
||||||
|
@ -1225,7 +1224,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(int(steps_per_circle) / 4))
|
current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle))
|
||||||
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.
|
||||||
|
@ -1254,7 +1253,7 @@ class Geometry(object):
|
||||||
QtWidgets.QApplication.processEvents()
|
QtWidgets.QApplication.processEvents()
|
||||||
|
|
||||||
# Can only result in a Polygon or MultiPolygon
|
# Can only result in a Polygon or MultiPolygon
|
||||||
current = current.buffer(-tooldia * (1 - overlap), int(int(steps_per_circle) / 4))
|
current = current.buffer(-tooldia * (1 - overlap), int(steps_per_circle))
|
||||||
if current.area > 0:
|
if current.area > 0:
|
||||||
|
|
||||||
# current can be a MultiPolygon
|
# current can be a MultiPolygon
|
||||||
|
@ -1372,11 +1371,12 @@ class Geometry(object):
|
||||||
|
|
||||||
# Clean inside edges (contours) of the original polygon
|
# Clean inside edges (contours) of the original polygon
|
||||||
if contour:
|
if contour:
|
||||||
outer_edges = [x.exterior for x in autolist(
|
outer_edges = [
|
||||||
polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4)))]
|
x.exterior for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle)))
|
||||||
|
]
|
||||||
inner_edges = []
|
inner_edges = []
|
||||||
# Over resulting polygons
|
# Over resulting polygons
|
||||||
for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4))):
|
for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle))):
|
||||||
for y in x.interiors: # Over interiors of each polygon
|
for y in x.interiors: # Over interiors of each polygon
|
||||||
inner_edges.append(y)
|
inner_edges.append(y)
|
||||||
# geoms += outer_edges + inner_edges
|
# geoms += outer_edges + inner_edges
|
||||||
|
@ -1626,7 +1626,7 @@ class Geometry(object):
|
||||||
# Straight line from current_pt to pt.
|
# Straight line from current_pt to pt.
|
||||||
# Is the toolpath inside the geometry?
|
# Is the toolpath inside the geometry?
|
||||||
walk_path = LineString([current_pt, pt])
|
walk_path = LineString([current_pt, pt])
|
||||||
walk_cut = walk_path.buffer(tooldia / 2, int(steps_per_circle / 4))
|
walk_cut = walk_path.buffer(tooldia / 2, int(steps_per_circle))
|
||||||
|
|
||||||
if walk_cut.within(boundary) and walk_path.length < max_walk:
|
if walk_cut.within(boundary) and walk_path.length < max_walk:
|
||||||
# log.debug("Walk to path #%d is inside. Joining." % path_count)
|
# log.debug("Walk to path #%d is inside. Joining." % path_count)
|
||||||
|
@ -4213,7 +4213,7 @@ class CNCjob(Geometry):
|
||||||
radius = np.sqrt(gobj['I']**2 + gobj['J']**2)
|
radius = np.sqrt(gobj['I']**2 + gobj['J']**2)
|
||||||
start = np.arctan2(-gobj['J'], -gobj['I'])
|
start = np.arctan2(-gobj['J'], -gobj['I'])
|
||||||
stop = np.arctan2(-center[1] + y, -center[0] + x)
|
stop = np.arctan2(-center[1] + y, -center[0] + x)
|
||||||
path += arc(center, radius, start, stop, arcdir[current['G']], int(self.steps_per_circle / 4))
|
path += arc(center, radius, start, stop, arcdir[current['G']], int(self.steps_per_circle))
|
||||||
|
|
||||||
current['X'] = x
|
current['X'] = x
|
||||||
current['Y'] = y
|
current['Y'] = y
|
||||||
|
@ -4362,8 +4362,7 @@ class CNCjob(Geometry):
|
||||||
visible=visible, layer=1)
|
visible=visible, layer=1)
|
||||||
else:
|
else:
|
||||||
# For Incremental coordinates type G91
|
# For Incremental coordinates type G91
|
||||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
self.app.inform.emit('[ERROR_NOTCL] %s' % _('G91 coordinates not implemented ...'))
|
||||||
_('G91 coordinates not implemented ...'))
|
|
||||||
for geo in gcode_parsed:
|
for geo in gcode_parsed:
|
||||||
if geo['kind'][0] == 'T':
|
if geo['kind'][0] == 'T':
|
||||||
current_position = geo['geom'].coords[0]
|
current_position = geo['geom'].coords[0]
|
||||||
|
|
|
@ -373,6 +373,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
QtGui.QIcon(self.app.resource_location + '/origin16.png'), _('Se&t Origin\tO'))
|
QtGui.QIcon(self.app.resource_location + '/origin16.png'), _('Se&t Origin\tO'))
|
||||||
self.menueditjump = self.menuedit.addAction(
|
self.menueditjump = self.menuedit.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/jump_to16.png'), _('Jump to Location\tJ'))
|
QtGui.QIcon(self.app.resource_location + '/jump_to16.png'), _('Jump to Location\tJ'))
|
||||||
|
self.menueditlocate = self.menuedit.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/locate16.png'), _('Locate in Object\tSHIFT+J'))
|
||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
self.menuedit.addSeparator()
|
self.menuedit.addSeparator()
|
||||||
|
@ -825,6 +827,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin'))
|
QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin'))
|
||||||
self.jmp_btn = self.toolbargeo.addAction(
|
self.jmp_btn = self.toolbargeo.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/jump_to16.png'), _('Jump to Location'))
|
QtGui.QIcon(self.app.resource_location + '/jump_to16.png'), _('Jump to Location'))
|
||||||
|
self.locate_btn = self.toolbargeo.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/locate32.png'), _('Locate in Object'))
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# ########################## View Toolbar# ###############################
|
# ########################## View Toolbar# ###############################
|
||||||
|
@ -859,6 +863,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
self.dblsided_btn = self.toolbartools.addAction(
|
self.dblsided_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/doubleside32.png'), _("2Sided Tool"))
|
QtGui.QIcon(self.app.resource_location + '/doubleside32.png'), _("2Sided Tool"))
|
||||||
|
self.align_btn = self.toolbartools.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/align32.png'), _("Align Objects Tool"))
|
||||||
|
self.extract_btn = self.toolbartools.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Extract Drills Tool"))
|
||||||
|
|
||||||
self.cutout_btn = self.toolbartools.addAction(
|
self.cutout_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/cut16_bis.png'), _("Cutout Tool"))
|
QtGui.QIcon(self.app.resource_location + '/cut16_bis.png'), _("Cutout Tool"))
|
||||||
self.ncc_btn = self.toolbartools.addAction(
|
self.ncc_btn = self.toolbartools.addAction(
|
||||||
|
@ -1474,6 +1483,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
<td height="20"><strong>SHIFT+G</strong></td>
|
<td height="20"><strong>SHIFT+G</strong></td>
|
||||||
<td> %s</td>
|
<td> %s</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr height="20">
|
||||||
|
<td height="20"><strong>SHIFT+J</strong></td>
|
||||||
|
<td> %s</td>
|
||||||
|
</tr>
|
||||||
<tr height="20">
|
<tr height="20">
|
||||||
<td height="20"><strong>SHIFT+M</strong></td>
|
<td height="20"><strong>SHIFT+M</strong></td>
|
||||||
<td> %s</td>
|
<td> %s</td>
|
||||||
|
@ -1506,6 +1519,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
<td height="20"> </td>
|
<td height="20"> </td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr height="20">
|
||||||
|
<td height="20"><strong>ALT+A</strong></td>
|
||||||
|
<td> %s</td>
|
||||||
|
</tr>
|
||||||
<tr height="20">
|
<tr height="20">
|
||||||
<td height="20"><strong>ALT+C</strong></td>
|
<td height="20"><strong>ALT+C</strong></td>
|
||||||
<td> %s</td>
|
<td> %s</td>
|
||||||
|
@ -1518,6 +1535,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
<td height="20"><strong>ALT+E</strong></td>
|
<td height="20"><strong>ALT+E</strong></td>
|
||||||
<td> %s</td>
|
<td> %s</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr height="20">
|
||||||
|
<td height="20"><strong>ALT+I</strong></td>
|
||||||
|
<td> %s</td>
|
||||||
|
</tr>
|
||||||
<tr height="20">
|
<tr height="20">
|
||||||
<td height="20"><strong>ALT+J</strong></td>
|
<td height="20"><strong>ALT+J</strong></td>
|
||||||
<td> %s</td>
|
<td> %s</td>
|
||||||
|
@ -1637,11 +1658,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
# SHIFT section
|
# SHIFT section
|
||||||
_("Copy Obj_Name"),
|
_("Copy Obj_Name"),
|
||||||
_("Toggle Code Editor"), _("Toggle the axis"), _("Distance Minimum Tool"), _("Open Preferences Window"),
|
_("Toggle Code Editor"), _("Toggle the axis"), _("Locate in Object"), _("Distance Minimum Tool"),
|
||||||
|
_("Open Preferences Window"),
|
||||||
_("Rotate by 90 degree CCW"), _("Run a Script"), _("Toggle the workspace"), _("Skew on X axis"),
|
_("Rotate by 90 degree CCW"), _("Run a Script"), _("Toggle the workspace"), _("Skew on X axis"),
|
||||||
_("Skew on Y axis"),
|
_("Skew on Y axis"),
|
||||||
# ALT section
|
# ALT section
|
||||||
_("Calculators Tool"), _("2-Sided PCB Tool"), _("Transformations Tool"), _("Fiducials Tool"),
|
_("Align Objects Tool"), _("Calculators Tool"), _("2-Sided PCB Tool"), _("Transformations Tool"),
|
||||||
|
_("Extract Drills Tool"), _("Fiducials Tool"),
|
||||||
_("Solder Paste Dispensing Tool"),
|
_("Solder Paste Dispensing Tool"),
|
||||||
_("Film PCB Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"),
|
_("Film PCB Tool"), _("Non-Copper Clearing Tool"), _("Optimal Tool"),
|
||||||
_("Paint Area Tool"), _("QRCode Tool"), _("Rules Check Tool"),
|
_("Paint Area Tool"), _("QRCode Tool"), _("Rules Check Tool"),
|
||||||
|
@ -2457,8 +2480,12 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin'))
|
QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin'))
|
||||||
self.jmp_btn = self.toolbargeo.addAction(
|
self.jmp_btn = self.toolbargeo.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/jump_to16.png'), _('Jump to Location'))
|
QtGui.QIcon(self.app.resource_location + '/jump_to16.png'), _('Jump to Location'))
|
||||||
|
self.locate_btn = self.toolbargeo.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/locate32.png'), _('Locate in Object'))
|
||||||
|
|
||||||
# ## View Toolbar # ##
|
# ########################################################################
|
||||||
|
# ########################## View Toolbar# ###############################
|
||||||
|
# ########################################################################
|
||||||
self.replot_btn = self.toolbarview.addAction(
|
self.replot_btn = self.toolbarview.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/replot32.png'), _("&Replot"))
|
QtGui.QIcon(self.app.resource_location + '/replot32.png'), _("&Replot"))
|
||||||
self.clear_plot_btn = self.toolbarview.addAction(
|
self.clear_plot_btn = self.toolbarview.addAction(
|
||||||
|
@ -2470,9 +2497,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
self.zoom_fit_btn = self.toolbarview.addAction(
|
self.zoom_fit_btn = self.toolbarview.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/zoom_fit32.png'), _("Zoom Fit"))
|
QtGui.QIcon(self.app.resource_location + '/zoom_fit32.png'), _("Zoom Fit"))
|
||||||
|
|
||||||
# self.toolbarview.setVisible(False)
|
# ########################################################################
|
||||||
|
# ########################## Shell Toolbar# ##############################
|
||||||
# ## Shell Toolbar # ##
|
# ########################################################################
|
||||||
self.shell_btn = self.toolbarshell.addAction(
|
self.shell_btn = self.toolbarshell.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/shell32.png'), _("&Command Line"))
|
QtGui.QIcon(self.app.resource_location + '/shell32.png'), _("&Command Line"))
|
||||||
self.new_script_btn = self.toolbarshell.addAction(
|
self.new_script_btn = self.toolbarshell.addAction(
|
||||||
|
@ -2485,6 +2512,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
# ## Tools Toolbar # ##
|
# ## Tools Toolbar # ##
|
||||||
self.dblsided_btn = self.toolbartools.addAction(
|
self.dblsided_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/doubleside32.png'), _("2Sided Tool"))
|
QtGui.QIcon(self.app.resource_location + '/doubleside32.png'), _("2Sided Tool"))
|
||||||
|
self.align_btn = self.toolbartools.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/align32.png'), _("Align Objects Tool"))
|
||||||
|
self.extract_btn = self.toolbartools.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Extract Drills Tool"))
|
||||||
|
|
||||||
self.cutout_btn = self.toolbartools.addAction(
|
self.cutout_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/cut16_bis.png'), _("&Cutout Tool"))
|
QtGui.QIcon(self.app.resource_location + '/cut16_bis.png'), _("&Cutout Tool"))
|
||||||
self.ncc_btn = self.toolbartools.addAction(
|
self.ncc_btn = self.toolbartools.addAction(
|
||||||
|
@ -2498,10 +2530,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
self.film_btn = self.toolbartools.addAction(
|
self.film_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/film16.png'), _("Film Tool"))
|
QtGui.QIcon(self.app.resource_location + '/film16.png'), _("Film Tool"))
|
||||||
self.solder_btn = self.toolbartools.addAction(
|
self.solder_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/solderpastebis32.png'),
|
QtGui.QIcon(self.app.resource_location + '/solderpastebis32.png'), _("SolderPaste Tool"))
|
||||||
_("SolderPaste Tool"))
|
|
||||||
self.sub_btn = self.toolbartools.addAction(
|
self.sub_btn = self.toolbartools.addAction(
|
||||||
QtGui.QIcon(self.app.resource_location + '/sub32.png'), _("Subtract Tool"))
|
QtGui.QIcon(self.app.resource_location + '/sub32.png'), _("Subtract Tool"))
|
||||||
|
self.rules_btn = self.toolbartools.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/rules32.png'), _("Rules Tool"))
|
||||||
|
self.optimal_btn = self.toolbartools.addAction(
|
||||||
|
QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Optimal Tool"))
|
||||||
|
|
||||||
self.toolbartools.addSeparator()
|
self.toolbartools.addSeparator()
|
||||||
|
|
||||||
|
@ -2834,6 +2869,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
if key == QtCore.Qt.Key_G:
|
if key == QtCore.Qt.Key_G:
|
||||||
self.app.on_toggle_axis()
|
self.app.on_toggle_axis()
|
||||||
|
|
||||||
|
# Locate in Object
|
||||||
|
if key == QtCore.Qt.Key_J:
|
||||||
|
self.app.on_locate(obj=self.app.collection.get_active())
|
||||||
|
|
||||||
# Run Distance Minimum Tool
|
# Run Distance Minimum Tool
|
||||||
if key == QtCore.Qt.Key_M:
|
if key == QtCore.Qt.Key_M:
|
||||||
self.app.distance_min_tool.run()
|
self.app.distance_min_tool.run()
|
||||||
|
@ -2882,6 +2921,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
if key == Qt.Key_3:
|
if key == Qt.Key_3:
|
||||||
self.app.disable_other_plots()
|
self.app.disable_other_plots()
|
||||||
|
|
||||||
|
# Align in Object Tool
|
||||||
|
if key == QtCore.Qt.Key_A:
|
||||||
|
self.app.align_objects_tool.run(toggle=True)
|
||||||
|
|
||||||
# Calculator Tool
|
# Calculator Tool
|
||||||
if key == QtCore.Qt.Key_C:
|
if key == QtCore.Qt.Key_C:
|
||||||
self.app.calculator_tool.run(toggle=True)
|
self.app.calculator_tool.run(toggle=True)
|
||||||
|
@ -2906,6 +2949,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
||||||
self.app.on_toggle_grid_lines()
|
self.app.on_toggle_grid_lines()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Align in Object Tool
|
||||||
|
if key == QtCore.Qt.Key_I:
|
||||||
|
self.app.edrills_tool.run(toggle=True)
|
||||||
|
|
||||||
# Fiducials Tool
|
# Fiducials Tool
|
||||||
if key == QtCore.Qt.Key_J:
|
if key == QtCore.Qt.Key_J:
|
||||||
self.app.fiducial_tool.run(toggle=True)
|
self.app.fiducial_tool.run(toggle=True)
|
||||||
|
|
|
@ -2009,6 +2009,10 @@ class FCTable(QtWidgets.QTableWidget):
|
||||||
palette = QtGui.QPalette()
|
palette = QtGui.QPalette()
|
||||||
palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight,
|
palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Highlight,
|
||||||
palette.color(QtGui.QPalette.Active, QtGui.QPalette.Highlight))
|
palette.color(QtGui.QPalette.Active, QtGui.QPalette.Highlight))
|
||||||
|
|
||||||
|
# make inactive rows text some color as active; may be useful in the future
|
||||||
|
# palette.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.HighlightedText,
|
||||||
|
# palette.color(QtGui.QPalette.Active, QtGui.QPalette.HighlightedText))
|
||||||
self.setPalette(palette)
|
self.setPalette(palette)
|
||||||
|
|
||||||
if drag_drop:
|
if drag_drop:
|
||||||
|
|
|
@ -32,11 +32,11 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
:param container: The parent container in which to draw plots.
|
:param container: The parent container in which to draw plots.
|
||||||
:rtype: PlotCanvas
|
:rtype: PlotCanvas
|
||||||
"""
|
"""
|
||||||
print("step_1_1")
|
|
||||||
|
|
||||||
super(PlotCanvas, self).__init__()
|
# super(PlotCanvas, self).__init__()
|
||||||
|
# QtCore.QObject.__init__(self)
|
||||||
# VisPyCanvas.__init__(self)
|
# VisPyCanvas.__init__(self)
|
||||||
print("step_1_2")
|
super().__init__()
|
||||||
|
|
||||||
# VisPyCanvas does not allow new attributes. Override.
|
# VisPyCanvas does not allow new attributes. Override.
|
||||||
self.unfreeze()
|
self.unfreeze()
|
||||||
|
@ -46,8 +46,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
# Parent container
|
# Parent container
|
||||||
self.container = container
|
self.container = container
|
||||||
|
|
||||||
print("step_1_3")
|
|
||||||
|
|
||||||
settings = QtCore.QSettings("Open Source", "FlatCAM")
|
settings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||||
if settings.contains("theme"):
|
if settings.contains("theme"):
|
||||||
theme = settings.value('theme', type=str)
|
theme = settings.value('theme', type=str)
|
||||||
|
@ -117,8 +115,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
print("step_1_4")
|
|
||||||
|
|
||||||
# <VisPyCanvas>
|
# <VisPyCanvas>
|
||||||
self.create_native()
|
self.create_native()
|
||||||
self.native.setParent(self.fcapp.ui)
|
self.native.setParent(self.fcapp.ui)
|
||||||
|
@ -126,8 +122,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
# <QtCore.QObject>
|
# <QtCore.QObject>
|
||||||
self.container.addWidget(self.native)
|
self.container.addWidget(self.native)
|
||||||
|
|
||||||
print("step_1_5")
|
|
||||||
|
|
||||||
# ## AXIS # ##
|
# ## AXIS # ##
|
||||||
self.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=True,
|
self.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=True,
|
||||||
parent=self.view.scene)
|
parent=self.view.scene)
|
||||||
|
@ -135,15 +129,11 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=False,
|
self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=False,
|
||||||
parent=self.view.scene)
|
parent=self.view.scene)
|
||||||
|
|
||||||
print("step_1_6")
|
|
||||||
|
|
||||||
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
|
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
|
||||||
# all CNC have a limited workspace
|
# all CNC have a limited workspace
|
||||||
if self.fcapp.defaults['global_workspace'] is True:
|
if self.fcapp.defaults['global_workspace'] is True:
|
||||||
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
|
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
|
||||||
|
|
||||||
print("step_1_7")
|
|
||||||
|
|
||||||
self.line_parent = None
|
self.line_parent = None
|
||||||
if self.fcapp.defaults["global_cursor_color_enabled"]:
|
if self.fcapp.defaults["global_cursor_color_enabled"]:
|
||||||
c_color = Color(self.fcapp.defaults["global_cursor_color"]).rgba
|
c_color = Color(self.fcapp.defaults["global_cursor_color"]).rgba
|
||||||
|
@ -156,8 +146,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
|
self.cursor_h_line = InfiniteLine(pos=None, color=c_color, vertical=False,
|
||||||
parent=self.line_parent)
|
parent=self.line_parent)
|
||||||
|
|
||||||
print("step_1_8")
|
|
||||||
|
|
||||||
self.shape_collections = []
|
self.shape_collections = []
|
||||||
|
|
||||||
self.shape_collection = self.new_shape_collection()
|
self.shape_collection = self.new_shape_collection()
|
||||||
|
@ -171,10 +159,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
||||||
self.big_cursor = None
|
self.big_cursor = None
|
||||||
# Keep VisPy canvas happy by letting it be "frozen" again.
|
# Keep VisPy canvas happy by letting it be "frozen" again.
|
||||||
self.freeze()
|
self.freeze()
|
||||||
print("step_1_9")
|
|
||||||
|
|
||||||
self.fit_view()
|
self.fit_view()
|
||||||
print("step_1_10")
|
|
||||||
|
|
||||||
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
|
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
|
||||||
|
|
||||||
|
|
|
@ -242,19 +242,23 @@ class Tools2PreferencesUI(QtWidgets.QWidget):
|
||||||
self.tools2_cal_group = Tools2CalPrefGroupUI(decimals=self.decimals)
|
self.tools2_cal_group = Tools2CalPrefGroupUI(decimals=self.decimals)
|
||||||
self.tools2_cal_group.setMinimumWidth(220)
|
self.tools2_cal_group.setMinimumWidth(220)
|
||||||
|
|
||||||
|
self.tools2_edrills_group = Tools2EDrillsPrefGroupUI(decimals=self.decimals)
|
||||||
|
self.tools2_edrills_group.setMinimumWidth(220)
|
||||||
|
|
||||||
self.vlay = QtWidgets.QVBoxLayout()
|
self.vlay = QtWidgets.QVBoxLayout()
|
||||||
self.vlay.addWidget(self.tools2_checkrules_group)
|
self.vlay.addWidget(self.tools2_checkrules_group)
|
||||||
self.vlay.addWidget(self.tools2_optimal_group)
|
self.vlay.addWidget(self.tools2_optimal_group)
|
||||||
|
|
||||||
self.vlay1 = QtWidgets.QVBoxLayout()
|
self.vlay1 = QtWidgets.QVBoxLayout()
|
||||||
self.vlay1.addWidget(self.tools2_qrcode_group)
|
self.vlay1.addWidget(self.tools2_qrcode_group)
|
||||||
|
self.vlay1.addWidget(self.tools2_fiducials_group)
|
||||||
|
|
||||||
self.vlay2 = QtWidgets.QVBoxLayout()
|
self.vlay2 = QtWidgets.QVBoxLayout()
|
||||||
self.vlay2.addWidget(self.tools2_cfill_group)
|
self.vlay2.addWidget(self.tools2_cfill_group)
|
||||||
|
|
||||||
self.vlay3 = QtWidgets.QVBoxLayout()
|
self.vlay3 = QtWidgets.QVBoxLayout()
|
||||||
self.vlay3.addWidget(self.tools2_fiducials_group)
|
|
||||||
self.vlay3.addWidget(self.tools2_cal_group)
|
self.vlay3.addWidget(self.tools2_cal_group)
|
||||||
|
self.vlay3.addWidget(self.tools2_edrills_group)
|
||||||
|
|
||||||
self.layout.addLayout(self.vlay)
|
self.layout.addLayout(self.vlay)
|
||||||
self.layout.addLayout(self.vlay1)
|
self.layout.addLayout(self.vlay1)
|
||||||
|
@ -333,7 +337,8 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||||
# Theme selection
|
# Theme selection
|
||||||
self.theme_label = QtWidgets.QLabel('%s:' % _('Theme'))
|
self.theme_label = QtWidgets.QLabel('%s:' % _('Theme'))
|
||||||
self.theme_label.setToolTip(
|
self.theme_label.setToolTip(
|
||||||
_("Select a theme for FlatCAM.")
|
_("Select a theme for FlatCAM.\n"
|
||||||
|
"It will theme the plot area.")
|
||||||
)
|
)
|
||||||
|
|
||||||
self.theme_radio = RadioSet([
|
self.theme_radio = RadioSet([
|
||||||
|
@ -356,6 +361,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
||||||
self.theme_button = FCButton(_("Apply Theme"))
|
self.theme_button = FCButton(_("Apply Theme"))
|
||||||
self.theme_button.setToolTip(
|
self.theme_button.setToolTip(
|
||||||
_("Select a theme for FlatCAM.\n"
|
_("Select a theme for FlatCAM.\n"
|
||||||
|
"It will theme the plot area.\n"
|
||||||
"The application will restart after change.")
|
"The application will restart after change.")
|
||||||
)
|
)
|
||||||
grid0.addWidget(self.theme_button, 2, 0, 1, 3)
|
grid0.addWidget(self.theme_button, 2, 0, 1, 3)
|
||||||
|
@ -1587,14 +1593,6 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||||
"After change, it will be applied at next App start.")
|
"After change, it will be applied at next App start.")
|
||||||
)
|
)
|
||||||
self.worker_number_sb = FCSpinner()
|
self.worker_number_sb = FCSpinner()
|
||||||
self.worker_number_sb.setToolTip(
|
|
||||||
_("The number of Qthreads made available to the App.\n"
|
|
||||||
"A bigger number may finish the jobs more quickly but\n"
|
|
||||||
"depending on your computer speed, may make the App\n"
|
|
||||||
"unresponsive. Can have a value between 2 and 16.\n"
|
|
||||||
"Default value is 2.\n"
|
|
||||||
"After change, it will be applied at next App start.")
|
|
||||||
)
|
|
||||||
self.worker_number_sb.set_range(2, 16)
|
self.worker_number_sb.set_range(2, 16)
|
||||||
|
|
||||||
grid0.addWidget(self.worker_number_label, 25, 0)
|
grid0.addWidget(self.worker_number_label, 25, 0)
|
||||||
|
@ -1604,21 +1602,13 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
||||||
tol_label = QtWidgets.QLabel('%s:' % _("Geo Tolerance"))
|
tol_label = QtWidgets.QLabel('%s:' % _("Geo Tolerance"))
|
||||||
tol_label.setToolTip(_(
|
tol_label.setToolTip(_(
|
||||||
"This value can counter the effect of the Circle Steps\n"
|
"This value can counter the effect of the Circle Steps\n"
|
||||||
"parameter. Default value is 0.01.\n"
|
"parameter. Default value is 0.005.\n"
|
||||||
"A lower value will increase the detail both in image\n"
|
"A lower value will increase the detail both in image\n"
|
||||||
"and in Gcode for the circles, with a higher cost in\n"
|
"and in Gcode for the circles, with a higher cost in\n"
|
||||||
"performance. Higher value will provide more\n"
|
"performance. Higher value will provide more\n"
|
||||||
"performance at the expense of level of detail."
|
"performance at the expense of level of detail."
|
||||||
))
|
))
|
||||||
self.tol_entry = FCDoubleSpinner()
|
self.tol_entry = FCDoubleSpinner()
|
||||||
self.tol_entry.setToolTip(_(
|
|
||||||
"This value can counter the effect of the Circle Steps\n"
|
|
||||||
"parameter. Default value is 0.01.\n"
|
|
||||||
"A lower value will increase the detail both in image\n"
|
|
||||||
"and in Gcode for the circles, with a higher cost in\n"
|
|
||||||
"performance. Higher value will provide more\n"
|
|
||||||
"performance at the expense of level of detail."
|
|
||||||
))
|
|
||||||
self.tol_entry.setSingleStep(0.001)
|
self.tol_entry.setSingleStep(0.001)
|
||||||
self.tol_entry.set_precision(6)
|
self.tol_entry.set_precision(6)
|
||||||
|
|
||||||
|
@ -7632,6 +7622,218 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
|
||||||
self.layout.addStretch()
|
self.layout.addStretch()
|
||||||
|
|
||||||
|
|
||||||
|
class Tools2EDrillsPrefGroupUI(OptionsGroupUI):
|
||||||
|
def __init__(self, decimals=4, parent=None):
|
||||||
|
|
||||||
|
super(Tools2EDrillsPrefGroupUI, self).__init__(self)
|
||||||
|
|
||||||
|
self.setTitle(str(_("Extract Drills Options")))
|
||||||
|
self.decimals = decimals
|
||||||
|
|
||||||
|
# ## Grid Layout
|
||||||
|
grid_lay = QtWidgets.QGridLayout()
|
||||||
|
self.layout.addLayout(grid_lay)
|
||||||
|
grid_lay.setColumnStretch(0, 0)
|
||||||
|
grid_lay.setColumnStretch(1, 1)
|
||||||
|
|
||||||
|
self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
|
||||||
|
self.param_label.setToolTip(
|
||||||
|
_("Parameters used for this tool.")
|
||||||
|
)
|
||||||
|
grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
|
||||||
|
|
||||||
|
self.padt_label = QtWidgets.QLabel("<b>%s:</b>" % _("Processed Pads Type"))
|
||||||
|
self.padt_label.setToolTip(
|
||||||
|
_("The type of pads shape to be processed.\n"
|
||||||
|
"If the PCB has many SMD pads with rectangular pads,\n"
|
||||||
|
"disable the Rectangular aperture.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.padt_label, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
# Circular Aperture Selection
|
||||||
|
self.circular_cb = FCCheckBox('%s' % _("Circular"))
|
||||||
|
self.circular_cb.setToolTip(
|
||||||
|
_("Create drills from circular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2)
|
||||||
|
|
||||||
|
# Oblong Aperture Selection
|
||||||
|
self.oblong_cb = FCCheckBox('%s' % _("Oblong"))
|
||||||
|
self.oblong_cb.setToolTip(
|
||||||
|
_("Create drills from oblong pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2)
|
||||||
|
|
||||||
|
# Square Aperture Selection
|
||||||
|
self.square_cb = FCCheckBox('%s' % _("Square"))
|
||||||
|
self.square_cb.setToolTip(
|
||||||
|
_("Create drills from square pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.square_cb, 5, 0, 1, 2)
|
||||||
|
|
||||||
|
# Rectangular Aperture Selection
|
||||||
|
self.rectangular_cb = FCCheckBox('%s' % _("Rectangular"))
|
||||||
|
self.rectangular_cb.setToolTip(
|
||||||
|
_("Create drills from rectangular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2)
|
||||||
|
|
||||||
|
# Others type of Apertures Selection
|
||||||
|
self.other_cb = FCCheckBox('%s' % _("Others"))
|
||||||
|
self.other_cb.setToolTip(
|
||||||
|
_("Create drills from other types of pad shape.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.other_cb, 7, 0, 1, 2)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid_lay.addWidget(separator_line, 8, 0, 1, 2)
|
||||||
|
|
||||||
|
# ## Axis
|
||||||
|
self.hole_size_radio = RadioSet(
|
||||||
|
[
|
||||||
|
{'label': _("Fixed Diameter"), 'value': 'fixed'},
|
||||||
|
{'label': _("Fixed Annular Ring"), 'value': 'ring'},
|
||||||
|
{'label': _("Proportional"), 'value': 'prop'}
|
||||||
|
],
|
||||||
|
orientation='vertical',
|
||||||
|
stretch=False)
|
||||||
|
self.hole_size_label = QtWidgets.QLabel('<b>%s:</b>' % _("Method"))
|
||||||
|
self.hole_size_label.setToolTip(
|
||||||
|
_("The selected method of extracting the drills. Can be:\n"
|
||||||
|
"- Fixed Diameter -> all holes will have a set size\n"
|
||||||
|
"- Fixed Annular Ring -> all holes will have a set annular ring\n"
|
||||||
|
"- Proportional -> each hole size will be a fraction of the pad size"))
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.hole_size_label, 9, 0)
|
||||||
|
grid_lay.addWidget(self.hole_size_radio, 9, 1)
|
||||||
|
|
||||||
|
# grid_lay1.addWidget(QtWidgets.QLabel(''))
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid_lay.addWidget(separator_line, 10, 0, 1, 2)
|
||||||
|
|
||||||
|
# Annular Ring
|
||||||
|
self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
|
||||||
|
grid_lay.addWidget(self.fixed_label, 11, 0, 1, 2)
|
||||||
|
|
||||||
|
# Diameter value
|
||||||
|
self.dia_entry = FCDoubleSpinner()
|
||||||
|
self.dia_entry.set_precision(self.decimals)
|
||||||
|
self.dia_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
self.dia_label = QtWidgets.QLabel('%s:' % _("value"))
|
||||||
|
self.dia_label.setToolTip(
|
||||||
|
_("Fixed hole diameter.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.dia_label, 12, 0)
|
||||||
|
grid_lay.addWidget(self.dia_entry, 12, 1)
|
||||||
|
|
||||||
|
# Annular Ring value
|
||||||
|
self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
|
||||||
|
self.ring_label.setToolTip(
|
||||||
|
_("The size of annular ring.\n"
|
||||||
|
"The copper sliver between the drill hole exterior\n"
|
||||||
|
"and the margin of the copper pad.")
|
||||||
|
)
|
||||||
|
grid_lay.addWidget(self.ring_label, 13, 0, 1, 2)
|
||||||
|
|
||||||
|
# Circular Annular Ring Value
|
||||||
|
self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
|
||||||
|
self.circular_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for circular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.circular_ring_entry = FCDoubleSpinner()
|
||||||
|
self.circular_ring_entry.set_precision(self.decimals)
|
||||||
|
self.circular_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.circular_ring_label, 14, 0)
|
||||||
|
grid_lay.addWidget(self.circular_ring_entry, 14, 1)
|
||||||
|
|
||||||
|
# Oblong Annular Ring Value
|
||||||
|
self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
|
||||||
|
self.oblong_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for oblong pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.oblong_ring_entry = FCDoubleSpinner()
|
||||||
|
self.oblong_ring_entry.set_precision(self.decimals)
|
||||||
|
self.oblong_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.oblong_ring_label, 15, 0)
|
||||||
|
grid_lay.addWidget(self.oblong_ring_entry, 15, 1)
|
||||||
|
|
||||||
|
# Square Annular Ring Value
|
||||||
|
self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
|
||||||
|
self.square_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for square pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.square_ring_entry = FCDoubleSpinner()
|
||||||
|
self.square_ring_entry.set_precision(self.decimals)
|
||||||
|
self.square_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.square_ring_label, 16, 0)
|
||||||
|
grid_lay.addWidget(self.square_ring_entry, 16, 1)
|
||||||
|
|
||||||
|
# Rectangular Annular Ring Value
|
||||||
|
self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
|
||||||
|
self.rectangular_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for rectangular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.rectangular_ring_entry = FCDoubleSpinner()
|
||||||
|
self.rectangular_ring_entry.set_precision(self.decimals)
|
||||||
|
self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.rectangular_ring_label, 17, 0)
|
||||||
|
grid_lay.addWidget(self.rectangular_ring_entry, 17, 1)
|
||||||
|
|
||||||
|
# Others Annular Ring Value
|
||||||
|
self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
|
||||||
|
self.other_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for other pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.other_ring_entry = FCDoubleSpinner()
|
||||||
|
self.other_ring_entry.set_precision(self.decimals)
|
||||||
|
self.other_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.other_ring_label, 18, 0)
|
||||||
|
grid_lay.addWidget(self.other_ring_entry, 18, 1)
|
||||||
|
|
||||||
|
self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
|
||||||
|
grid_lay.addWidget(self.prop_label, 19, 0, 1, 2)
|
||||||
|
|
||||||
|
# Factor value
|
||||||
|
self.factor_entry = FCDoubleSpinner(suffix='%')
|
||||||
|
self.factor_entry.set_precision(self.decimals)
|
||||||
|
self.factor_entry.set_range(0.0000, 100.0000)
|
||||||
|
self.factor_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
self.factor_label = QtWidgets.QLabel('%s:' % _("Factor"))
|
||||||
|
self.factor_label.setToolTip(
|
||||||
|
_("Proportional Diameter.\n"
|
||||||
|
"The drill diameter will be a fraction of the pad size.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.factor_label, 20, 0)
|
||||||
|
grid_lay.addWidget(self.factor_entry, 20, 1)
|
||||||
|
|
||||||
|
self.layout.addStretch()
|
||||||
|
|
||||||
|
|
||||||
class FAExcPrefGroupUI(OptionsGroupUI):
|
class FAExcPrefGroupUI(OptionsGroupUI):
|
||||||
def __init__(self, decimals=4, parent=None):
|
def __init__(self, decimals=4, parent=None):
|
||||||
# OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)
|
# OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)
|
||||||
|
|
|
@ -24,7 +24,8 @@ black = Color("#000000")
|
||||||
class VisPyCanvas(scene.SceneCanvas):
|
class VisPyCanvas(scene.SceneCanvas):
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None):
|
||||||
scene.SceneCanvas.__init__(self, keys=None, config=config)
|
# scene.SceneCanvas.__init__(self, keys=None, config=config)
|
||||||
|
super().__init__(config=config, keys=None)
|
||||||
|
|
||||||
self.unfreeze()
|
self.unfreeze()
|
||||||
|
|
||||||
|
|
|
@ -595,6 +595,7 @@ class Gerber(Geometry):
|
||||||
match = self.units_re.search(gline)
|
match = self.units_re.search(gline)
|
||||||
if match:
|
if match:
|
||||||
obs_gerber_units = {'0': 'IN', '1': 'MM'}[match.group(1)]
|
obs_gerber_units = {'0': 'IN', '1': 'MM'}[match.group(1)]
|
||||||
|
self.units = obs_gerber_units
|
||||||
log.warning("Gerber obsolete units found = %s" % obs_gerber_units)
|
log.warning("Gerber obsolete units found = %s" % obs_gerber_units)
|
||||||
# Changed for issue #80
|
# Changed for issue #80
|
||||||
# self.convert_units({'0': 'IN', '1': 'MM'}[match.group(1)])
|
# self.convert_units({'0': 'IN', '1': 'MM'}[match.group(1)])
|
||||||
|
@ -834,7 +835,8 @@ class Gerber(Geometry):
|
||||||
# --- Buffered ---
|
# --- Buffered ---
|
||||||
geo_dict = dict()
|
geo_dict = dict()
|
||||||
if current_aperture in self.apertures:
|
if current_aperture in self.apertures:
|
||||||
buff_value = float(self.apertures[current_aperture]['size']) / 2.0
|
# the following line breaks loading of Circuit Studio Gerber files
|
||||||
|
# buff_value = float(self.apertures[current_aperture]['size']) / 2.0
|
||||||
# region_geo = Polygon(path).buffer(buff_value, int(self.steps_per_circle))
|
# region_geo = Polygon(path).buffer(buff_value, int(self.steps_per_circle))
|
||||||
region_geo = Polygon(path) # Sprint Layout Gerbers with ground fill are crashed with above
|
region_geo = Polygon(path) # Sprint Layout Gerbers with ground fill are crashed with above
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1,499 @@
|
||||||
|
# ##########################################################
|
||||||
|
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||||
|
# File Author: Marius Adrian Stanciu (c) #
|
||||||
|
# Date: 1/13/2020 #
|
||||||
|
# MIT Licence #
|
||||||
|
# ##########################################################
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||||
|
from FlatCAMTool import FlatCAMTool
|
||||||
|
|
||||||
|
from flatcamGUI.GUIElements import FCComboBox, RadioSet
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from shapely.geometry import Point
|
||||||
|
from shapely.affinity import translate
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import FlatCAMTranslation as fcTranslate
|
||||||
|
import builtins
|
||||||
|
import logging
|
||||||
|
|
||||||
|
fcTranslate.apply_language('strings')
|
||||||
|
if '_' not in builtins.__dict__:
|
||||||
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
log = logging.getLogger('base')
|
||||||
|
|
||||||
|
|
||||||
|
class AlignObjects(FlatCAMTool):
|
||||||
|
|
||||||
|
toolName = _("Align Objects")
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
FlatCAMTool.__init__(self, app)
|
||||||
|
|
||||||
|
self.app = app
|
||||||
|
self.decimals = app.decimals
|
||||||
|
|
||||||
|
self.canvas = self.app.plotcanvas
|
||||||
|
|
||||||
|
# ## Title
|
||||||
|
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||||
|
title_label.setStyleSheet("""
|
||||||
|
QLabel
|
||||||
|
{
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.layout.addWidget(title_label)
|
||||||
|
|
||||||
|
# Form Layout
|
||||||
|
grid0 = QtWidgets.QGridLayout()
|
||||||
|
grid0.setColumnStretch(0, 0)
|
||||||
|
grid0.setColumnStretch(1, 1)
|
||||||
|
self.layout.addLayout(grid0)
|
||||||
|
|
||||||
|
self.aligned_label = QtWidgets.QLabel('<b>%s</b>' % _("Selection of the WORKING object"))
|
||||||
|
grid0.addWidget(self.aligned_label, 0, 0, 1, 2)
|
||||||
|
|
||||||
|
# Type of object to be aligned
|
||||||
|
self.type_obj_combo = FCComboBox()
|
||||||
|
self.type_obj_combo.addItem("Gerber")
|
||||||
|
self.type_obj_combo.addItem("Excellon")
|
||||||
|
|
||||||
|
self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
|
||||||
|
self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
|
||||||
|
|
||||||
|
self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
|
||||||
|
self.type_obj_combo_label.setToolTip(
|
||||||
|
_("Specify the type of object to be aligned.\n"
|
||||||
|
"It can be of type: Gerber or Excellon.\n"
|
||||||
|
"The selection here decide the type of objects that will be\n"
|
||||||
|
"in the Object combobox.")
|
||||||
|
)
|
||||||
|
grid0.addWidget(self.type_obj_combo_label, 2, 0)
|
||||||
|
grid0.addWidget(self.type_obj_combo, 2, 1)
|
||||||
|
|
||||||
|
# Object to be aligned
|
||||||
|
self.object_combo = FCComboBox()
|
||||||
|
self.object_combo.setModel(self.app.collection)
|
||||||
|
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
|
self.object_combo.setCurrentIndex(1)
|
||||||
|
|
||||||
|
self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
|
||||||
|
self.object_label.setToolTip(
|
||||||
|
_("Object to be aligned.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid0.addWidget(self.object_label, 3, 0)
|
||||||
|
grid0.addWidget(self.object_combo, 3, 1)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid0.addWidget(separator_line, 4, 0, 1, 2)
|
||||||
|
|
||||||
|
self.aligned_label = QtWidgets.QLabel('<b>%s</b>' % _("Selection of the TARGET object"))
|
||||||
|
self.aligned_label.setToolTip(
|
||||||
|
_("Object to which the other objects will be aligned to (moved to).")
|
||||||
|
)
|
||||||
|
grid0.addWidget(self.aligned_label, 6, 0, 1, 2)
|
||||||
|
|
||||||
|
# Type of object to be aligned to = aligner
|
||||||
|
self.type_aligner_obj_combo = FCComboBox()
|
||||||
|
self.type_aligner_obj_combo.addItem("Gerber")
|
||||||
|
self.type_aligner_obj_combo.addItem("Excellon")
|
||||||
|
|
||||||
|
self.type_aligner_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png"))
|
||||||
|
self.type_aligner_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png"))
|
||||||
|
|
||||||
|
self.type_aligner_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
|
||||||
|
self.type_aligner_obj_combo_label.setToolTip(
|
||||||
|
_("Specify the type of object to be aligned to.\n"
|
||||||
|
"It can be of type: Gerber or Excellon.\n"
|
||||||
|
"The selection here decide the type of objects that will be\n"
|
||||||
|
"in the Object combobox.")
|
||||||
|
)
|
||||||
|
grid0.addWidget(self.type_aligner_obj_combo_label, 7, 0)
|
||||||
|
grid0.addWidget(self.type_aligner_obj_combo, 7, 1)
|
||||||
|
|
||||||
|
# Object to be aligned to = aligner
|
||||||
|
self.aligner_object_combo = FCComboBox()
|
||||||
|
self.aligner_object_combo.setModel(self.app.collection)
|
||||||
|
self.aligner_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
|
self.aligner_object_combo.setCurrentIndex(1)
|
||||||
|
|
||||||
|
self.aligner_object_label = QtWidgets.QLabel('%s:' % _("Object"))
|
||||||
|
self.aligner_object_label.setToolTip(
|
||||||
|
_("Object to be aligned to. Aligner.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid0.addWidget(self.aligner_object_label, 8, 0)
|
||||||
|
grid0.addWidget(self.aligner_object_combo, 8, 1)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid0.addWidget(separator_line, 9, 0, 1, 2)
|
||||||
|
|
||||||
|
# Alignment Type
|
||||||
|
self.a_type_lbl = QtWidgets.QLabel('<b>%s:</b>' % _("Alignment Type"))
|
||||||
|
self.a_type_lbl.setToolTip(
|
||||||
|
_("The type of alignment can be:\n"
|
||||||
|
"- Single Point -> it require a single point of sync, the action will be a translation\n"
|
||||||
|
"- Dual Point -> it require two points of sync, the action will be translation followed by rotation")
|
||||||
|
)
|
||||||
|
self.a_type_radio = RadioSet(
|
||||||
|
[
|
||||||
|
{'label': _('Single Point'), 'value': 'sp'},
|
||||||
|
{'label': _('Dual Point'), 'value': 'dp'}
|
||||||
|
],
|
||||||
|
orientation='horizontal',
|
||||||
|
stretch=False
|
||||||
|
)
|
||||||
|
|
||||||
|
grid0.addWidget(self.a_type_lbl, 10, 0, 1, 2)
|
||||||
|
grid0.addWidget(self.a_type_radio, 11, 0, 1, 2)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid0.addWidget(separator_line, 12, 0, 1, 2)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
self.align_object_button = QtWidgets.QPushButton(_("Align Object"))
|
||||||
|
self.align_object_button.setToolTip(
|
||||||
|
_("Align the specified object to the aligner object.\n"
|
||||||
|
"If only one point is used then it assumes translation.\n"
|
||||||
|
"If tho points are used it assume translation and rotation.")
|
||||||
|
)
|
||||||
|
self.align_object_button.setStyleSheet("""
|
||||||
|
QPushButton
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.layout.addWidget(self.align_object_button)
|
||||||
|
|
||||||
|
self.layout.addStretch()
|
||||||
|
|
||||||
|
# ## Reset Tool
|
||||||
|
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
|
||||||
|
self.reset_button.setToolTip(
|
||||||
|
_("Will reset the tool parameters.")
|
||||||
|
)
|
||||||
|
self.reset_button.setStyleSheet("""
|
||||||
|
QPushButton
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.layout.addWidget(self.reset_button)
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
self.align_object_button.clicked.connect(self.on_align)
|
||||||
|
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||||
|
self.type_aligner_obj_combo.currentIndexChanged.connect(self.on_type_aligner_index_changed)
|
||||||
|
self.reset_button.clicked.connect(self.set_tool_ui)
|
||||||
|
|
||||||
|
self.mr = None
|
||||||
|
|
||||||
|
# if the mouse events are connected to a local method set this True
|
||||||
|
self.local_connected = False
|
||||||
|
|
||||||
|
# store the status of the grid
|
||||||
|
self.grid_status_memory = None
|
||||||
|
|
||||||
|
self.aligned_obj = None
|
||||||
|
self.aligner_obj = None
|
||||||
|
|
||||||
|
# this is one of the objects: self.aligned_obj or self.aligner_obj
|
||||||
|
self.target_obj = None
|
||||||
|
|
||||||
|
# here store the alignment points
|
||||||
|
self.clicked_points = list()
|
||||||
|
|
||||||
|
self.align_type = None
|
||||||
|
|
||||||
|
# old colors of objects involved in the alignment
|
||||||
|
self.aligner_old_fill_color = None
|
||||||
|
self.aligner_old_line_color = None
|
||||||
|
self.aligned_old_fill_color = None
|
||||||
|
self.aligned_old_line_color = None
|
||||||
|
|
||||||
|
def run(self, toggle=True):
|
||||||
|
self.app.report_usage("ToolAlignObjects()")
|
||||||
|
|
||||||
|
if toggle:
|
||||||
|
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||||
|
if self.app.ui.splitter.sizes()[0] == 0:
|
||||||
|
self.app.ui.splitter.setSizes([1, 1])
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||||
|
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||||
|
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||||
|
# focus on Tool Tab
|
||||||
|
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||||
|
else:
|
||||||
|
self.app.ui.splitter.setSizes([0, 1])
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.app.ui.splitter.sizes()[0] == 0:
|
||||||
|
self.app.ui.splitter.setSizes([1, 1])
|
||||||
|
|
||||||
|
FlatCAMTool.run(self)
|
||||||
|
self.set_tool_ui()
|
||||||
|
|
||||||
|
self.app.ui.notebook.setTabText(2, _("Align Tool"))
|
||||||
|
|
||||||
|
def install(self, icon=None, separator=None, **kwargs):
|
||||||
|
FlatCAMTool.install(self, icon, separator, shortcut='ALT+A', **kwargs)
|
||||||
|
|
||||||
|
def set_tool_ui(self):
|
||||||
|
self.reset_fields()
|
||||||
|
|
||||||
|
self.clicked_points = list()
|
||||||
|
self.target_obj = None
|
||||||
|
self.aligned_obj = None
|
||||||
|
self.aligner_obj = None
|
||||||
|
|
||||||
|
self.aligner_old_fill_color = None
|
||||||
|
self.aligner_old_line_color = None
|
||||||
|
self.aligned_old_fill_color = None
|
||||||
|
self.aligned_old_line_color = None
|
||||||
|
|
||||||
|
self.a_type_radio.set_value(self.app.defaults["tools_align_objects_align_type"])
|
||||||
|
|
||||||
|
if self.local_connected is True:
|
||||||
|
self.disconnect_cal_events()
|
||||||
|
|
||||||
|
def on_type_obj_index_changed(self):
|
||||||
|
obj_type = self.type_obj_combo.currentIndex()
|
||||||
|
self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||||
|
self.object_combo.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def on_type_aligner_index_changed(self):
|
||||||
|
obj_type = self.type_aligner_obj_combo.currentIndex()
|
||||||
|
self.aligner_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||||
|
self.aligner_object_combo.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def on_align(self):
|
||||||
|
self.app.delete_selection_shape()
|
||||||
|
|
||||||
|
obj_sel_index = self.object_combo.currentIndex()
|
||||||
|
obj_model_index = self.app.collection.index(obj_sel_index, 0, self.object_combo.rootModelIndex())
|
||||||
|
try:
|
||||||
|
self.aligned_obj = obj_model_index.internalPointer().obj
|
||||||
|
except AttributeError:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no aligned FlatCAM object selected..."))
|
||||||
|
return
|
||||||
|
|
||||||
|
aligner_obj_sel_index = self.aligner_object_combo.currentIndex()
|
||||||
|
aligner_obj_model_index = self.app.collection.index(
|
||||||
|
aligner_obj_sel_index, 0, self.aligner_object_combo.rootModelIndex())
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.aligner_obj = aligner_obj_model_index.internalPointer().obj
|
||||||
|
except AttributeError:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no aligner FlatCAM object selected..."))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.align_type = self.a_type_radio.get_value()
|
||||||
|
|
||||||
|
# disengage the grid snapping since it will be hard to find the drills or pads on grid
|
||||||
|
if self.app.ui.grid_snap_btn.isChecked():
|
||||||
|
self.grid_status_memory = True
|
||||||
|
self.app.ui.grid_snap_btn.trigger()
|
||||||
|
else:
|
||||||
|
self.grid_status_memory = False
|
||||||
|
|
||||||
|
self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
|
||||||
|
|
||||||
|
if self.app.is_legacy is False:
|
||||||
|
self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||||
|
else:
|
||||||
|
self.canvas.graph_event_disconnect(self.app.mr)
|
||||||
|
|
||||||
|
self.local_connected = True
|
||||||
|
|
||||||
|
self.aligner_old_fill_color = self.aligner_obj.fill_color
|
||||||
|
self.aligner_old_line_color = self.aligner_obj.outline_color
|
||||||
|
self.aligned_old_fill_color = self.aligned_obj.fill_color
|
||||||
|
self.aligned_old_line_color = self.aligned_obj.outline_color
|
||||||
|
|
||||||
|
self.app.inform.emit('%s: %s' % (_("First Point"), _("Click on the START point.")))
|
||||||
|
self.target_obj = self.aligned_obj
|
||||||
|
self.set_color()
|
||||||
|
|
||||||
|
def on_mouse_click_release(self, event):
|
||||||
|
if self.app.is_legacy is False:
|
||||||
|
event_pos = event.pos
|
||||||
|
right_button = 2
|
||||||
|
self.app.event_is_dragging = self.app.event_is_dragging
|
||||||
|
else:
|
||||||
|
event_pos = (event.xdata, event.ydata)
|
||||||
|
right_button = 3
|
||||||
|
self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning
|
||||||
|
|
||||||
|
pos_canvas = self.canvas.translate_coords(event_pos)
|
||||||
|
|
||||||
|
if event.button == 1:
|
||||||
|
click_pt = Point([pos_canvas[0], pos_canvas[1]])
|
||||||
|
|
||||||
|
if self.app.selection_type is not None:
|
||||||
|
# delete previous selection shape
|
||||||
|
self.app.delete_selection_shape()
|
||||||
|
self.app.selection_type = None
|
||||||
|
else:
|
||||||
|
if self.target_obj.kind.lower() == 'excellon':
|
||||||
|
for tool, tool_dict in self.target_obj.tools.items():
|
||||||
|
for geo in tool_dict['solid_geometry']:
|
||||||
|
if click_pt.within(geo):
|
||||||
|
center_pt = geo.centroid
|
||||||
|
self.clicked_points.append(
|
||||||
|
[
|
||||||
|
float('%.*f' % (self.decimals, center_pt.x)),
|
||||||
|
float('%.*f' % (self.decimals, center_pt.y))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.check_points()
|
||||||
|
elif self.target_obj.kind.lower() == 'gerber':
|
||||||
|
for apid, apid_val in self.target_obj.apertures.items():
|
||||||
|
for geo_el in apid_val['geometry']:
|
||||||
|
if 'solid' in geo_el:
|
||||||
|
if click_pt.within(geo_el['solid']):
|
||||||
|
if isinstance(geo_el['follow'], Point):
|
||||||
|
center_pt = geo_el['solid'].centroid
|
||||||
|
self.clicked_points.append(
|
||||||
|
[
|
||||||
|
float('%.*f' % (self.decimals, center_pt.x)),
|
||||||
|
float('%.*f' % (self.decimals, center_pt.y))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.check_points()
|
||||||
|
|
||||||
|
elif event.button == right_button and self.app.event_is_dragging is False:
|
||||||
|
self.reset_color()
|
||||||
|
self.clicked_points = list()
|
||||||
|
self.disconnect_cal_events()
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request."))
|
||||||
|
|
||||||
|
def check_points(self):
|
||||||
|
if len(self.clicked_points) == 1:
|
||||||
|
self.app.inform.emit('%s: %s. %s' % (
|
||||||
|
_("First Point"), _("Click on the DESTINATION point."), _(" Or right click to cancel.")))
|
||||||
|
self.target_obj = self.aligner_obj
|
||||||
|
self.reset_color()
|
||||||
|
self.set_color()
|
||||||
|
|
||||||
|
if len(self.clicked_points) == 2:
|
||||||
|
if self.align_type == 'sp':
|
||||||
|
self.align_translate()
|
||||||
|
self.app.inform.emit('[success] %s' % _("Done."))
|
||||||
|
self.app.plot_all()
|
||||||
|
|
||||||
|
self.disconnect_cal_events()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.app.inform.emit('%s: %s. %s' % (
|
||||||
|
_("Second Point"), _("Click on the START point."), _(" Or right click to cancel.")))
|
||||||
|
self.target_obj = self.aligned_obj
|
||||||
|
self.reset_color()
|
||||||
|
self.set_color()
|
||||||
|
|
||||||
|
if len(self.clicked_points) == 3:
|
||||||
|
self.app.inform.emit('%s: %s. %s' % (
|
||||||
|
_("Second Point"), _("Click on the DESTINATION point."), _(" Or right click to cancel.")))
|
||||||
|
self.target_obj = self.aligner_obj
|
||||||
|
self.reset_color()
|
||||||
|
self.set_color()
|
||||||
|
|
||||||
|
if len(self.clicked_points) == 4:
|
||||||
|
self.align_translate()
|
||||||
|
self.align_rotate()
|
||||||
|
self.app.inform.emit('[success] %s' % _("Done."))
|
||||||
|
|
||||||
|
self.disconnect_cal_events()
|
||||||
|
self.app.plot_all()
|
||||||
|
|
||||||
|
def align_translate(self):
|
||||||
|
dx = self.clicked_points[1][0] - self.clicked_points[0][0]
|
||||||
|
dy = self.clicked_points[1][1] - self.clicked_points[0][1]
|
||||||
|
|
||||||
|
self.aligned_obj.offset((dx, dy))
|
||||||
|
|
||||||
|
# Update the object bounding box options
|
||||||
|
a, b, c, d = self.aligned_obj.bounds()
|
||||||
|
self.aligned_obj.options['xmin'] = a
|
||||||
|
self.aligned_obj.options['ymin'] = b
|
||||||
|
self.aligned_obj.options['xmax'] = c
|
||||||
|
self.aligned_obj.options['ymax'] = d
|
||||||
|
|
||||||
|
def align_rotate(self):
|
||||||
|
dx = self.clicked_points[1][0] - self.clicked_points[0][0]
|
||||||
|
dy = self.clicked_points[1][1] - self.clicked_points[0][1]
|
||||||
|
|
||||||
|
test_rotation_pt = translate(Point(self.clicked_points[2]), xoff=dx, yoff=dy)
|
||||||
|
new_start = (test_rotation_pt.x, test_rotation_pt.y)
|
||||||
|
new_dest = self.clicked_points[3]
|
||||||
|
|
||||||
|
origin_pt = self.clicked_points[1]
|
||||||
|
|
||||||
|
dxd = new_dest[0] - origin_pt[0]
|
||||||
|
dyd = new_dest[1] - origin_pt[1]
|
||||||
|
|
||||||
|
dxs = new_start[0] - origin_pt[0]
|
||||||
|
dys = new_start[1] - origin_pt[1]
|
||||||
|
|
||||||
|
rotation_not_needed = (abs(new_start[0] - new_dest[0]) <= (10 ** -self.decimals)) or \
|
||||||
|
(abs(new_start[1] - new_dest[1]) <= (10 ** -self.decimals))
|
||||||
|
if rotation_not_needed is False:
|
||||||
|
# calculate rotation angle
|
||||||
|
angle_dest = math.degrees(math.atan(dyd / dxd))
|
||||||
|
angle_start = math.degrees(math.atan(dys / dxs))
|
||||||
|
angle = angle_dest - angle_start
|
||||||
|
self.aligned_obj.rotate(angle=angle, point=origin_pt)
|
||||||
|
|
||||||
|
def disconnect_cal_events(self):
|
||||||
|
# restore the Grid snapping if it was active before
|
||||||
|
if self.grid_status_memory is True:
|
||||||
|
self.app.ui.grid_snap_btn.trigger()
|
||||||
|
|
||||||
|
self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||||
|
|
||||||
|
if self.app.is_legacy is False:
|
||||||
|
self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
|
||||||
|
else:
|
||||||
|
self.canvas.graph_event_disconnect(self.mr)
|
||||||
|
|
||||||
|
self.local_connected = False
|
||||||
|
|
||||||
|
self.aligner_old_fill_color = None
|
||||||
|
self.aligner_old_line_color = None
|
||||||
|
self.aligned_old_fill_color = None
|
||||||
|
self.aligned_old_line_color = None
|
||||||
|
|
||||||
|
def set_color(self):
|
||||||
|
new_color = "#15678abf"
|
||||||
|
new_line_color = new_color
|
||||||
|
self.target_obj.shapes.redraw(
|
||||||
|
update_colors=(new_color, new_line_color)
|
||||||
|
)
|
||||||
|
|
||||||
|
def reset_color(self):
|
||||||
|
self.aligned_obj.shapes.redraw(
|
||||||
|
update_colors=(self.aligned_old_fill_color, self.aligned_old_line_color)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.aligner_obj.shapes.redraw(
|
||||||
|
update_colors=(self.aligner_old_fill_color, self.aligner_old_line_color)
|
||||||
|
)
|
||||||
|
|
||||||
|
def reset_fields(self):
|
||||||
|
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
|
self.aligner_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
|
@ -533,16 +533,17 @@ class DblSidedTool(FlatCAMTool):
|
||||||
"Add them and retry."))
|
"Add them and retry."))
|
||||||
return
|
return
|
||||||
|
|
||||||
drills = []
|
drills = list()
|
||||||
|
|
||||||
for hole in holes:
|
for hole in holes:
|
||||||
point = Point(hole)
|
point = Point(hole)
|
||||||
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
||||||
drills.append({"point": point, "tool": "1"})
|
drills.append({"point": point, "tool": "1"})
|
||||||
drills.append({"point": point_mirror, "tool": "1"})
|
drills.append({"point": point_mirror, "tool": "1"})
|
||||||
if 'solid_geometry' not in tools:
|
if 'solid_geometry' not in tools["1"]:
|
||||||
tools["1"]['solid_geometry'] = []
|
tools["1"]['solid_geometry'] = list()
|
||||||
else:
|
else:
|
||||||
|
tools["1"]['solid_geometry'].append(point)
|
||||||
tools["1"]['solid_geometry'].append(point_mirror)
|
tools["1"]['solid_geometry'].append(point_mirror)
|
||||||
|
|
||||||
def obj_init(obj_inst, app_inst):
|
def obj_init(obj_inst, app_inst):
|
||||||
|
|
|
@ -361,11 +361,12 @@ class Distance(FlatCAMTool):
|
||||||
self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
|
self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
|
||||||
self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
|
self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
|
||||||
|
|
||||||
try:
|
if dx != 0.0:
|
||||||
angle = math.degrees(math.atan(dy / dx))
|
try:
|
||||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
angle = math.degrees(math.atan(dy / dx))
|
||||||
except Exception as e:
|
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||||
pass
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
|
self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
|
||||||
self.app.ui.rel_position_label.setText(
|
self.app.ui.rel_position_label.setText(
|
||||||
|
@ -424,11 +425,13 @@ class Distance(FlatCAMTool):
|
||||||
if len(self.points) == 1:
|
if len(self.points) == 1:
|
||||||
self.utility_geometry(pos=pos)
|
self.utility_geometry(pos=pos)
|
||||||
# and display the temporary angle
|
# and display the temporary angle
|
||||||
try:
|
if dx != 0.0:
|
||||||
angle = math.degrees(math.atan(dy / dx))
|
try:
|
||||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
angle = math.degrees(math.atan(dy / dx))
|
||||||
except Exception as e:
|
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||||
pass
|
except Exception as e:
|
||||||
|
log.debug("Distance.on_mouse_move_meas() -> update utility geometry -> %s" % str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug("Distance.on_mouse_move_meas() --> %s" % str(e))
|
log.debug("Distance.on_mouse_move_meas() --> %s" % str(e))
|
||||||
|
|
|
@ -0,0 +1,697 @@
|
||||||
|
# ##########################################################
|
||||||
|
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||||
|
# File Author: Marius Adrian Stanciu (c) #
|
||||||
|
# Date: 1/10/2020 #
|
||||||
|
# MIT Licence #
|
||||||
|
# ##########################################################
|
||||||
|
|
||||||
|
from PyQt5 import QtWidgets, QtCore
|
||||||
|
|
||||||
|
from FlatCAMTool import FlatCAMTool
|
||||||
|
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox
|
||||||
|
|
||||||
|
from shapely.geometry import Point
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import gettext
|
||||||
|
import FlatCAMTranslation as fcTranslate
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
fcTranslate.apply_language('strings')
|
||||||
|
if '_' not in builtins.__dict__:
|
||||||
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
log = logging.getLogger('base')
|
||||||
|
|
||||||
|
|
||||||
|
class ToolExtractDrills(FlatCAMTool):
|
||||||
|
|
||||||
|
toolName = _("Extract Drills")
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
FlatCAMTool.__init__(self, app)
|
||||||
|
self.decimals = self.app.decimals
|
||||||
|
|
||||||
|
# ## Title
|
||||||
|
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||||
|
title_label.setStyleSheet("""
|
||||||
|
QLabel
|
||||||
|
{
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.layout.addWidget(title_label)
|
||||||
|
|
||||||
|
self.empty_lb = QtWidgets.QLabel("")
|
||||||
|
self.layout.addWidget(self.empty_lb)
|
||||||
|
|
||||||
|
# ## Grid Layout
|
||||||
|
grid_lay = QtWidgets.QGridLayout()
|
||||||
|
self.layout.addLayout(grid_lay)
|
||||||
|
grid_lay.setColumnStretch(0, 1)
|
||||||
|
grid_lay.setColumnStretch(1, 0)
|
||||||
|
|
||||||
|
# ## Gerber Object
|
||||||
|
self.gerber_object_combo = QtWidgets.QComboBox()
|
||||||
|
self.gerber_object_combo.setModel(self.app.collection)
|
||||||
|
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
|
self.gerber_object_combo.setCurrentIndex(1)
|
||||||
|
|
||||||
|
self.grb_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
|
||||||
|
self.grb_label.setToolTip('%s.' % _("Gerber from which to extract drill holes"))
|
||||||
|
|
||||||
|
# grid_lay.addRow("Bottom Layer:", self.object_combo)
|
||||||
|
grid_lay.addWidget(self.grb_label, 0, 0, 1, 2)
|
||||||
|
grid_lay.addWidget(self.gerber_object_combo, 1, 0, 1, 2)
|
||||||
|
|
||||||
|
self.padt_label = QtWidgets.QLabel("<b>%s</b>" % _("Processed Pads Type"))
|
||||||
|
self.padt_label.setToolTip(
|
||||||
|
_("The type of pads shape to be processed.\n"
|
||||||
|
"If the PCB has many SMD pads with rectangular pads,\n"
|
||||||
|
"disable the Rectangular aperture.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.padt_label, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
# Circular Aperture Selection
|
||||||
|
self.circular_cb = FCCheckBox('%s' % _("Circular"))
|
||||||
|
self.circular_cb.setToolTip(
|
||||||
|
_("Create drills from circular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.circular_cb, 3, 0, 1, 2)
|
||||||
|
|
||||||
|
# Oblong Aperture Selection
|
||||||
|
self.oblong_cb = FCCheckBox('%s' % _("Oblong"))
|
||||||
|
self.oblong_cb.setToolTip(
|
||||||
|
_("Create drills from oblong pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.oblong_cb, 4, 0, 1, 2)
|
||||||
|
|
||||||
|
# Square Aperture Selection
|
||||||
|
self.square_cb = FCCheckBox('%s' % _("Square"))
|
||||||
|
self.square_cb.setToolTip(
|
||||||
|
_("Create drills from square pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.square_cb, 5, 0, 1, 2)
|
||||||
|
|
||||||
|
# Rectangular Aperture Selection
|
||||||
|
self.rectangular_cb = FCCheckBox('%s' % _("Rectangular"))
|
||||||
|
self.rectangular_cb.setToolTip(
|
||||||
|
_("Create drills from rectangular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.rectangular_cb, 6, 0, 1, 2)
|
||||||
|
|
||||||
|
# Others type of Apertures Selection
|
||||||
|
self.other_cb = FCCheckBox('%s' % _("Others"))
|
||||||
|
self.other_cb.setToolTip(
|
||||||
|
_("Create drills from other types of pad shape.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid_lay.addWidget(self.other_cb, 7, 0, 1, 2)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid_lay.addWidget(separator_line, 8, 0, 1, 2)
|
||||||
|
|
||||||
|
# ## Grid Layout
|
||||||
|
grid1 = QtWidgets.QGridLayout()
|
||||||
|
self.layout.addLayout(grid1)
|
||||||
|
grid1.setColumnStretch(0, 0)
|
||||||
|
grid1.setColumnStretch(1, 1)
|
||||||
|
|
||||||
|
self.method_label = QtWidgets.QLabel('<b>%s</b>' % _("Method"))
|
||||||
|
grid1.addWidget(self.method_label, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
# ## Axis
|
||||||
|
self.hole_size_radio = RadioSet(
|
||||||
|
[
|
||||||
|
{'label': _("Fixed Diameter"), 'value': 'fixed'},
|
||||||
|
{'label': _("Fixed Annular Ring"), 'value': 'ring'},
|
||||||
|
{'label': _("Proportional"), 'value': 'prop'}
|
||||||
|
],
|
||||||
|
orientation='vertical',
|
||||||
|
stretch=False)
|
||||||
|
|
||||||
|
self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size"))
|
||||||
|
self.hole_size_label.setToolTip(
|
||||||
|
_("The selected method of extracting the drills. Can be:\n"
|
||||||
|
"- Fixed Diameter -> all holes will have a set size\n"
|
||||||
|
"- Fixed Annular Ring -> all holes will have a set annular ring\n"
|
||||||
|
"- Proportional -> each hole size will be a fraction of the pad size"))
|
||||||
|
|
||||||
|
grid1.addWidget(self.hole_size_label, 3, 0)
|
||||||
|
grid1.addWidget(self.hole_size_radio, 3, 1)
|
||||||
|
|
||||||
|
# grid_lay1.addWidget(QtWidgets.QLabel(''))
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid1.addWidget(separator_line, 5, 0, 1, 2)
|
||||||
|
|
||||||
|
# Annular Ring
|
||||||
|
self.fixed_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Diameter"))
|
||||||
|
grid1.addWidget(self.fixed_label, 6, 0, 1, 2)
|
||||||
|
|
||||||
|
# Diameter value
|
||||||
|
self.dia_entry = FCDoubleSpinner()
|
||||||
|
self.dia_entry.set_precision(self.decimals)
|
||||||
|
self.dia_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
self.dia_label = QtWidgets.QLabel('%s:' % _("Value"))
|
||||||
|
self.dia_label.setToolTip(
|
||||||
|
_("Fixed hole diameter.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid1.addWidget(self.dia_label, 8, 0)
|
||||||
|
grid1.addWidget(self.dia_entry, 8, 1)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid1.addWidget(separator_line, 9, 0, 1, 2)
|
||||||
|
|
||||||
|
self.ring_frame = QtWidgets.QFrame()
|
||||||
|
self.ring_frame.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.layout.addWidget(self.ring_frame)
|
||||||
|
|
||||||
|
self.ring_box = QtWidgets.QVBoxLayout()
|
||||||
|
self.ring_box.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.ring_frame.setLayout(self.ring_box)
|
||||||
|
|
||||||
|
# ## Grid Layout
|
||||||
|
grid2 = QtWidgets.QGridLayout()
|
||||||
|
grid2.setColumnStretch(0, 0)
|
||||||
|
grid2.setColumnStretch(1, 1)
|
||||||
|
self.ring_box.addLayout(grid2)
|
||||||
|
|
||||||
|
# Annular Ring value
|
||||||
|
self.ring_label = QtWidgets.QLabel('<b>%s</b>' % _("Fixed Annular Ring"))
|
||||||
|
self.ring_label.setToolTip(
|
||||||
|
_("The size of annular ring.\n"
|
||||||
|
"The copper sliver between the drill hole exterior\n"
|
||||||
|
"and the margin of the copper pad.")
|
||||||
|
)
|
||||||
|
grid2.addWidget(self.ring_label, 0, 0, 1, 2)
|
||||||
|
|
||||||
|
# Circular Annular Ring Value
|
||||||
|
self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular"))
|
||||||
|
self.circular_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for circular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.circular_ring_entry = FCDoubleSpinner()
|
||||||
|
self.circular_ring_entry.set_precision(self.decimals)
|
||||||
|
self.circular_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid2.addWidget(self.circular_ring_label, 1, 0)
|
||||||
|
grid2.addWidget(self.circular_ring_entry, 1, 1)
|
||||||
|
|
||||||
|
# Oblong Annular Ring Value
|
||||||
|
self.oblong_ring_label = QtWidgets.QLabel('%s:' % _("Oblong"))
|
||||||
|
self.oblong_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for oblong pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.oblong_ring_entry = FCDoubleSpinner()
|
||||||
|
self.oblong_ring_entry.set_precision(self.decimals)
|
||||||
|
self.oblong_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid2.addWidget(self.oblong_ring_label, 2, 0)
|
||||||
|
grid2.addWidget(self.oblong_ring_entry, 2, 1)
|
||||||
|
|
||||||
|
# Square Annular Ring Value
|
||||||
|
self.square_ring_label = QtWidgets.QLabel('%s:' % _("Square"))
|
||||||
|
self.square_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for square pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.square_ring_entry = FCDoubleSpinner()
|
||||||
|
self.square_ring_entry.set_precision(self.decimals)
|
||||||
|
self.square_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid2.addWidget(self.square_ring_label, 3, 0)
|
||||||
|
grid2.addWidget(self.square_ring_entry, 3, 1)
|
||||||
|
|
||||||
|
# Rectangular Annular Ring Value
|
||||||
|
self.rectangular_ring_label = QtWidgets.QLabel('%s:' % _("Rectangular"))
|
||||||
|
self.rectangular_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for rectangular pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.rectangular_ring_entry = FCDoubleSpinner()
|
||||||
|
self.rectangular_ring_entry.set_precision(self.decimals)
|
||||||
|
self.rectangular_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid2.addWidget(self.rectangular_ring_label, 4, 0)
|
||||||
|
grid2.addWidget(self.rectangular_ring_entry, 4, 1)
|
||||||
|
|
||||||
|
# Others Annular Ring Value
|
||||||
|
self.other_ring_label = QtWidgets.QLabel('%s:' % _("Others"))
|
||||||
|
self.other_ring_label.setToolTip(
|
||||||
|
_("The size of annular ring for other pads.")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.other_ring_entry = FCDoubleSpinner()
|
||||||
|
self.other_ring_entry.set_precision(self.decimals)
|
||||||
|
self.other_ring_entry.set_range(0.0000, 9999.9999)
|
||||||
|
|
||||||
|
grid2.addWidget(self.other_ring_label, 5, 0)
|
||||||
|
grid2.addWidget(self.other_ring_entry, 5, 1)
|
||||||
|
|
||||||
|
grid3 = QtWidgets.QGridLayout()
|
||||||
|
self.layout.addLayout(grid3)
|
||||||
|
grid3.setColumnStretch(0, 0)
|
||||||
|
grid3.setColumnStretch(1, 1)
|
||||||
|
|
||||||
|
separator_line = QtWidgets.QFrame()
|
||||||
|
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||||
|
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||||
|
grid3.addWidget(separator_line, 1, 0, 1, 2)
|
||||||
|
|
||||||
|
# Annular Ring value
|
||||||
|
self.prop_label = QtWidgets.QLabel('<b>%s</b>' % _("Proportional Diameter"))
|
||||||
|
grid3.addWidget(self.prop_label, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
# Diameter value
|
||||||
|
self.factor_entry = FCDoubleSpinner(suffix='%')
|
||||||
|
self.factor_entry.set_precision(self.decimals)
|
||||||
|
self.factor_entry.set_range(0.0000, 100.0000)
|
||||||
|
self.factor_entry.setSingleStep(0.1)
|
||||||
|
|
||||||
|
self.factor_label = QtWidgets.QLabel('%s:' % _("Value"))
|
||||||
|
self.factor_label.setToolTip(
|
||||||
|
_("Proportional Diameter.\n"
|
||||||
|
"The drill diameter will be a fraction of the pad size.")
|
||||||
|
)
|
||||||
|
|
||||||
|
grid3.addWidget(self.factor_label, 3, 0)
|
||||||
|
grid3.addWidget(self.factor_entry, 3, 1)
|
||||||
|
|
||||||
|
# Extract drills from Gerber apertures flashes (pads)
|
||||||
|
self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills"))
|
||||||
|
self.e_drills_button.setToolTip(
|
||||||
|
_("Extract drills from a given Gerber file.")
|
||||||
|
)
|
||||||
|
self.e_drills_button.setStyleSheet("""
|
||||||
|
QPushButton
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.layout.addWidget(self.e_drills_button)
|
||||||
|
|
||||||
|
self.layout.addStretch()
|
||||||
|
|
||||||
|
# ## Reset Tool
|
||||||
|
self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
|
||||||
|
self.reset_button.setToolTip(
|
||||||
|
_("Will reset the tool parameters.")
|
||||||
|
)
|
||||||
|
self.reset_button.setStyleSheet("""
|
||||||
|
QPushButton
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.layout.addWidget(self.reset_button)
|
||||||
|
|
||||||
|
self.circular_ring_entry.setEnabled(False)
|
||||||
|
self.oblong_ring_entry.setEnabled(False)
|
||||||
|
self.square_ring_entry.setEnabled(False)
|
||||||
|
self.rectangular_ring_entry.setEnabled(False)
|
||||||
|
self.other_ring_entry.setEnabled(False)
|
||||||
|
|
||||||
|
self.dia_entry.setDisabled(True)
|
||||||
|
self.dia_label.setDisabled(True)
|
||||||
|
self.factor_label.setDisabled(True)
|
||||||
|
self.factor_entry.setDisabled(True)
|
||||||
|
|
||||||
|
self.ring_frame.setDisabled(True)
|
||||||
|
|
||||||
|
# ## Signals
|
||||||
|
self.hole_size_radio.activated_custom.connect(self.on_hole_size_toggle)
|
||||||
|
self.e_drills_button.clicked.connect(self.on_extract_drills_click)
|
||||||
|
self.reset_button.clicked.connect(self.set_tool_ui)
|
||||||
|
|
||||||
|
self.circular_cb.stateChanged.connect(
|
||||||
|
lambda state:
|
||||||
|
self.circular_ring_entry.setDisabled(False) if state else self.circular_ring_entry.setDisabled(True)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.oblong_cb.stateChanged.connect(
|
||||||
|
lambda state:
|
||||||
|
self.oblong_ring_entry.setDisabled(False) if state else self.oblong_ring_entry.setDisabled(True)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.square_cb.stateChanged.connect(
|
||||||
|
lambda state:
|
||||||
|
self.square_ring_entry.setDisabled(False) if state else self.square_ring_entry.setDisabled(True)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.rectangular_cb.stateChanged.connect(
|
||||||
|
lambda state:
|
||||||
|
self.rectangular_ring_entry.setDisabled(False) if state else self.rectangular_ring_entry.setDisabled(True)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.other_cb.stateChanged.connect(
|
||||||
|
lambda state:
|
||||||
|
self.other_ring_entry.setDisabled(False) if state else self.other_ring_entry.setDisabled(True)
|
||||||
|
)
|
||||||
|
|
||||||
|
def install(self, icon=None, separator=None, **kwargs):
|
||||||
|
FlatCAMTool.install(self, icon, separator, shortcut='ALT+I', **kwargs)
|
||||||
|
|
||||||
|
def run(self, toggle=True):
|
||||||
|
self.app.report_usage("Extract Drills()")
|
||||||
|
|
||||||
|
if toggle:
|
||||||
|
# if the splitter is hidden, display it, else hide it but only if the current widget is the same
|
||||||
|
if self.app.ui.splitter.sizes()[0] == 0:
|
||||||
|
self.app.ui.splitter.setSizes([1, 1])
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||||
|
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||||
|
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||||
|
# focus on Tool Tab
|
||||||
|
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||||
|
else:
|
||||||
|
self.app.ui.splitter.setSizes([0, 1])
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if self.app.ui.splitter.sizes()[0] == 0:
|
||||||
|
self.app.ui.splitter.setSizes([1, 1])
|
||||||
|
|
||||||
|
FlatCAMTool.run(self)
|
||||||
|
self.set_tool_ui()
|
||||||
|
|
||||||
|
self.app.ui.notebook.setTabText(2, _("Extract Drills Tool"))
|
||||||
|
|
||||||
|
def set_tool_ui(self):
|
||||||
|
self.reset_fields()
|
||||||
|
|
||||||
|
self.hole_size_radio.set_value(self.app.defaults["tools_edrills_hole_type"])
|
||||||
|
|
||||||
|
self.dia_entry.set_value(float(self.app.defaults["tools_edrills_hole_fixed_dia"]))
|
||||||
|
|
||||||
|
self.circular_ring_entry.set_value(float(self.app.defaults["tools_edrills_circular_ring"]))
|
||||||
|
self.oblong_ring_entry.set_value(float(self.app.defaults["tools_edrills_oblong_ring"]))
|
||||||
|
self.square_ring_entry.set_value(float(self.app.defaults["tools_edrills_square_ring"]))
|
||||||
|
self.rectangular_ring_entry.set_value(float(self.app.defaults["tools_edrills_rectangular_ring"]))
|
||||||
|
self.other_ring_entry.set_value(float(self.app.defaults["tools_edrills_others_ring"]))
|
||||||
|
|
||||||
|
self.circular_cb.set_value(self.app.defaults["tools_edrills_circular"])
|
||||||
|
self.oblong_cb.set_value(self.app.defaults["tools_edrills_oblong"])
|
||||||
|
self.square_cb.set_value(self.app.defaults["tools_edrills_square"])
|
||||||
|
self.rectangular_cb.set_value(self.app.defaults["tools_edrills_rectangular"])
|
||||||
|
self.other_cb.set_value(self.app.defaults["tools_edrills_others"])
|
||||||
|
|
||||||
|
self.factor_entry.set_value(float(self.app.defaults["tools_edrills_hole_prop_factor"]))
|
||||||
|
|
||||||
|
def on_extract_drills_click(self):
|
||||||
|
|
||||||
|
drill_dia = self.dia_entry.get_value()
|
||||||
|
circ_r_val = self.circular_ring_entry.get_value()
|
||||||
|
oblong_r_val = self.oblong_ring_entry.get_value()
|
||||||
|
square_r_val = self.square_ring_entry.get_value()
|
||||||
|
rect_r_val = self.rectangular_ring_entry.get_value()
|
||||||
|
other_r_val = self.other_ring_entry.get_value()
|
||||||
|
|
||||||
|
prop_factor = self.factor_entry.get_value() / 100.0
|
||||||
|
|
||||||
|
drills = list()
|
||||||
|
tools = dict()
|
||||||
|
|
||||||
|
selection_index = self.gerber_object_combo.currentIndex()
|
||||||
|
model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
|
||||||
|
|
||||||
|
try:
|
||||||
|
fcobj = model_index.internalPointer().obj
|
||||||
|
except Exception:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||||
|
return
|
||||||
|
|
||||||
|
outname = fcobj.options['name'].rpartition('.')[0]
|
||||||
|
|
||||||
|
mode = self.hole_size_radio.get_value()
|
||||||
|
|
||||||
|
if mode == 'fixed':
|
||||||
|
tools = {"1": {"C": drill_dia}}
|
||||||
|
for apid, apid_value in fcobj.apertures.items():
|
||||||
|
ap_type = apid_value['type']
|
||||||
|
|
||||||
|
if ap_type == 'C':
|
||||||
|
if self.circular_cb.get_value() is False:
|
||||||
|
continue
|
||||||
|
elif ap_type == 'O':
|
||||||
|
if self.oblong_cb.get_value() is False:
|
||||||
|
continue
|
||||||
|
elif ap_type == 'R':
|
||||||
|
width = float(apid_value['width'])
|
||||||
|
height = float(apid_value['height'])
|
||||||
|
|
||||||
|
# if the height == width (float numbers so the reason for the following)
|
||||||
|
if round(width, self.decimals) == round(height, self.decimals):
|
||||||
|
if self.square_cb.get_value() is False:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if self.rectangular_cb.get_value() is False:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if self.other_cb.get_value() is False:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for geo_el in apid_value['geometry']:
|
||||||
|
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||||
|
drills.append({"point": geo_el['follow'], "tool": "1"})
|
||||||
|
if 'solid_geometry' not in tools["1"]:
|
||||||
|
tools["1"]['solid_geometry'] = list()
|
||||||
|
else:
|
||||||
|
tools["1"]['solid_geometry'].append(geo_el['follow'])
|
||||||
|
|
||||||
|
if 'solid_geometry' not in tools["1"] or not tools["1"]['solid_geometry']:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||||
|
return
|
||||||
|
elif mode == 'ring':
|
||||||
|
drills_found = set()
|
||||||
|
for apid, apid_value in fcobj.apertures.items():
|
||||||
|
ap_type = apid_value['type']
|
||||||
|
|
||||||
|
dia = None
|
||||||
|
if ap_type == 'C':
|
||||||
|
if self.circular_cb.get_value():
|
||||||
|
dia = float(apid_value['size']) - (2 * circ_r_val)
|
||||||
|
elif ap_type == 'O':
|
||||||
|
width = float(apid_value['width'])
|
||||||
|
height = float(apid_value['height'])
|
||||||
|
if self.oblong_cb.get_value():
|
||||||
|
if width > height:
|
||||||
|
dia = float(apid_value['height']) - (2 * oblong_r_val)
|
||||||
|
else:
|
||||||
|
dia = float(apid_value['width']) - (2 * oblong_r_val)
|
||||||
|
elif ap_type == 'R':
|
||||||
|
width = float(apid_value['width'])
|
||||||
|
height = float(apid_value['height'])
|
||||||
|
|
||||||
|
# if the height == width (float numbers so the reason for the following)
|
||||||
|
if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
|
||||||
|
(10 ** -self.decimals):
|
||||||
|
if self.square_cb.get_value():
|
||||||
|
dia = float(apid_value['height']) - (2 * square_r_val)
|
||||||
|
else:
|
||||||
|
if self.rectangular_cb.get_value():
|
||||||
|
if width > height:
|
||||||
|
dia = float(apid_value['height']) - (2 * rect_r_val)
|
||||||
|
else:
|
||||||
|
dia = float(apid_value['width']) - (2 * rect_r_val)
|
||||||
|
else:
|
||||||
|
if self.other_cb.get_value():
|
||||||
|
try:
|
||||||
|
dia = float(apid_value['size']) - (2 * other_r_val)
|
||||||
|
except KeyError:
|
||||||
|
if ap_type == 'AM':
|
||||||
|
pol = apid_value['geometry'][0]['solid']
|
||||||
|
x0, y0, x1, y1 = pol.bounds
|
||||||
|
dx = x1 - x0
|
||||||
|
dy = y1 - y0
|
||||||
|
if dx <= dy:
|
||||||
|
dia = dx - (2 * other_r_val)
|
||||||
|
else:
|
||||||
|
dia = dy - (2 * other_r_val)
|
||||||
|
|
||||||
|
# if dia is None then none of the above applied so we skip the following
|
||||||
|
if dia is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tool_in_drills = False
|
||||||
|
for tool, tool_val in tools.items():
|
||||||
|
if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
|
||||||
|
(10 ** -self.decimals):
|
||||||
|
tool_in_drills = tool
|
||||||
|
|
||||||
|
if tool_in_drills is False:
|
||||||
|
if tools:
|
||||||
|
new_tool = max([int(t) for t in tools]) + 1
|
||||||
|
tool_in_drills = str(new_tool)
|
||||||
|
else:
|
||||||
|
tool_in_drills = "1"
|
||||||
|
|
||||||
|
for geo_el in apid_value['geometry']:
|
||||||
|
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||||
|
if tool_in_drills not in tools:
|
||||||
|
tools[tool_in_drills] = {"C": dia}
|
||||||
|
|
||||||
|
drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
|
||||||
|
|
||||||
|
if 'solid_geometry' not in tools[tool_in_drills]:
|
||||||
|
tools[tool_in_drills]['solid_geometry'] = list()
|
||||||
|
else:
|
||||||
|
tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
|
||||||
|
|
||||||
|
if tool_in_drills in tools:
|
||||||
|
if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
|
||||||
|
drills_found.add(False)
|
||||||
|
else:
|
||||||
|
drills_found.add(True)
|
||||||
|
|
||||||
|
if True not in drills_found:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
drills_found = set()
|
||||||
|
for apid, apid_value in fcobj.apertures.items():
|
||||||
|
ap_type = apid_value['type']
|
||||||
|
|
||||||
|
dia = None
|
||||||
|
if ap_type == 'C':
|
||||||
|
if self.circular_cb.get_value():
|
||||||
|
dia = float(apid_value['size']) * prop_factor
|
||||||
|
elif ap_type == 'O':
|
||||||
|
width = float(apid_value['width'])
|
||||||
|
height = float(apid_value['height'])
|
||||||
|
if self.oblong_cb.get_value():
|
||||||
|
if width > height:
|
||||||
|
dia = float(apid_value['height']) * prop_factor
|
||||||
|
else:
|
||||||
|
dia = float(apid_value['width']) * prop_factor
|
||||||
|
elif ap_type == 'R':
|
||||||
|
width = float(apid_value['width'])
|
||||||
|
height = float(apid_value['height'])
|
||||||
|
|
||||||
|
# if the height == width (float numbers so the reason for the following)
|
||||||
|
if abs(float('%.*f' % (self.decimals, width)) - float('%.*f' % (self.decimals, height))) < \
|
||||||
|
(10 ** -self.decimals):
|
||||||
|
if self.square_cb.get_value():
|
||||||
|
dia = float(apid_value['height']) * prop_factor
|
||||||
|
else:
|
||||||
|
if self.rectangular_cb.get_value():
|
||||||
|
if width > height:
|
||||||
|
dia = float(apid_value['height']) * prop_factor
|
||||||
|
else:
|
||||||
|
dia = float(apid_value['width']) * prop_factor
|
||||||
|
else:
|
||||||
|
if self.other_cb.get_value():
|
||||||
|
try:
|
||||||
|
dia = float(apid_value['size']) * prop_factor
|
||||||
|
except KeyError:
|
||||||
|
if ap_type == 'AM':
|
||||||
|
pol = apid_value['geometry'][0]['solid']
|
||||||
|
x0, y0, x1, y1 = pol.bounds
|
||||||
|
dx = x1 - x0
|
||||||
|
dy = y1 - y0
|
||||||
|
if dx <= dy:
|
||||||
|
dia = dx * prop_factor
|
||||||
|
else:
|
||||||
|
dia = dy * prop_factor
|
||||||
|
|
||||||
|
# if dia is None then none of the above applied so we skip the following
|
||||||
|
if dia is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tool_in_drills = False
|
||||||
|
for tool, tool_val in tools.items():
|
||||||
|
if abs(float('%.*f' % (self.decimals, tool_val["C"])) - float('%.*f' % (self.decimals, dia))) < \
|
||||||
|
(10 ** -self.decimals):
|
||||||
|
tool_in_drills = tool
|
||||||
|
|
||||||
|
if tool_in_drills is False:
|
||||||
|
if tools:
|
||||||
|
new_tool = max([int(t) for t in tools]) + 1
|
||||||
|
tool_in_drills = str(new_tool)
|
||||||
|
else:
|
||||||
|
tool_in_drills = "1"
|
||||||
|
|
||||||
|
for geo_el in apid_value['geometry']:
|
||||||
|
if 'follow' in geo_el and isinstance(geo_el['follow'], Point):
|
||||||
|
if tool_in_drills not in tools:
|
||||||
|
tools[tool_in_drills] = {"C": dia}
|
||||||
|
|
||||||
|
drills.append({"point": geo_el['follow'], "tool": tool_in_drills})
|
||||||
|
|
||||||
|
if 'solid_geometry' not in tools[tool_in_drills]:
|
||||||
|
tools[tool_in_drills]['solid_geometry'] = list()
|
||||||
|
else:
|
||||||
|
tools[tool_in_drills]['solid_geometry'].append(geo_el['follow'])
|
||||||
|
|
||||||
|
if tool_in_drills in tools:
|
||||||
|
if 'solid_geometry' not in tools[tool_in_drills] or not tools[tool_in_drills]['solid_geometry']:
|
||||||
|
drills_found.add(False)
|
||||||
|
else:
|
||||||
|
drills_found.add(True)
|
||||||
|
|
||||||
|
if True not in drills_found:
|
||||||
|
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No drills extracted. Try different parameters."))
|
||||||
|
return
|
||||||
|
|
||||||
|
def obj_init(obj_inst, app_inst):
|
||||||
|
obj_inst.tools = tools
|
||||||
|
obj_inst.drills = drills
|
||||||
|
obj_inst.create_geometry()
|
||||||
|
obj_inst.source_file = self.app.export_excellon(obj_name=outname, local_use=obj_inst, filename=None,
|
||||||
|
use_thread=False)
|
||||||
|
|
||||||
|
self.app.new_object("excellon", outname, obj_init)
|
||||||
|
|
||||||
|
def on_hole_size_toggle(self, val):
|
||||||
|
if val == "fixed":
|
||||||
|
self.fixed_label.setDisabled(False)
|
||||||
|
self.dia_entry.setDisabled(False)
|
||||||
|
self.dia_label.setDisabled(False)
|
||||||
|
|
||||||
|
self.ring_frame.setDisabled(True)
|
||||||
|
|
||||||
|
self.prop_label.setDisabled(True)
|
||||||
|
self.factor_label.setDisabled(True)
|
||||||
|
self.factor_entry.setDisabled(True)
|
||||||
|
elif val == "ring":
|
||||||
|
self.fixed_label.setDisabled(True)
|
||||||
|
self.dia_entry.setDisabled(True)
|
||||||
|
self.dia_label.setDisabled(True)
|
||||||
|
|
||||||
|
self.ring_frame.setDisabled(False)
|
||||||
|
|
||||||
|
self.prop_label.setDisabled(True)
|
||||||
|
self.factor_label.setDisabled(True)
|
||||||
|
self.factor_entry.setDisabled(True)
|
||||||
|
elif val == "prop":
|
||||||
|
self.fixed_label.setDisabled(True)
|
||||||
|
self.dia_entry.setDisabled(True)
|
||||||
|
self.dia_label.setDisabled(True)
|
||||||
|
|
||||||
|
self.ring_frame.setDisabled(True)
|
||||||
|
|
||||||
|
self.prop_label.setDisabled(False)
|
||||||
|
self.factor_label.setDisabled(False)
|
||||||
|
self.factor_entry.setDisabled(False)
|
||||||
|
|
||||||
|
def reset_fields(self):
|
||||||
|
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||||
|
self.gerber_object_combo.setCurrentIndex(0)
|
|
@ -752,7 +752,7 @@ class Film(FlatCAMTool):
|
||||||
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
|
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
|
||||||
skew_reference=skew_reference,
|
skew_reference=skew_reference,
|
||||||
mirror=mirror,
|
mirror=mirror,
|
||||||
pagesize=pagesize, orientation=orientation, color=color, opacity=1.0,
|
pagesize_val=pagesize, orientation_val=orientation, color_val=color, opacity_val=1.0,
|
||||||
ftype=ftype
|
ftype=ftype
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1080,23 +1080,28 @@ class Film(FlatCAMTool):
|
||||||
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
|
skew_factor_x=None, skew_factor_y=None, skew_reference='center',
|
||||||
mirror=None, orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
|
mirror=None, orientation_val='p', pagesize_val='A4', color_val='black', opacity_val=1.0,
|
||||||
use_thread=True, ftype='svg'):
|
use_thread=True, ftype='svg'):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Exports a Geometry Object to an SVG file in positive black.
|
Exports a Geometry Object to an SVG file in positive black.
|
||||||
|
|
||||||
:param obj_name: the name of the FlatCAM object to be saved as SVG
|
:param obj_name: the name of the FlatCAM object to be saved
|
||||||
:param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
|
:param box_name: the name of the FlatCAM object to be used as delimitation of the content to be saved
|
||||||
:param filename: Path to the SVG file to save to.
|
:param filename: Path to the file to save to.
|
||||||
:param scale_stroke_factor: factor by which to change/scale the thickness of the features
|
:param scale_stroke_factor: factor by which to change/scale the thickness of the features
|
||||||
:param scale_factor_x: factor to scale the svg geometry on the X axis
|
:param scale_factor_x: factor to scale the geometry on the X axis
|
||||||
:param scale_factor_y: factor to scale the svg geometry on the Y axis
|
:param scale_factor_y: factor to scale the geometry on the Y axis
|
||||||
:param skew_factor_x: factor to skew the svg geometry on the X axis
|
:param skew_factor_x: factor to skew the geometry on the X axis
|
||||||
:param skew_factor_y: factor to skew the svg geometry on the Y axis
|
:param skew_factor_y: factor to skew the geometry on the Y axis
|
||||||
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', 'topright' and
|
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft',
|
||||||
those are the 4 points of the bounding box of the geometry to be skewed.
|
'topright' and those are the 4 points of the bounding box of the geometry to be skewed.
|
||||||
:param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
|
:param mirror: can be 'x' or 'y' or 'both'. Axis on which to mirror the svg geometry
|
||||||
|
:param orientation_val:
|
||||||
|
:param pagesize_val:
|
||||||
|
:param color_val:
|
||||||
|
:param opacity_val:
|
||||||
|
:param use_thread: if to be run in a separate thread; boolean
|
||||||
|
:param ftype: the type of file for saving the film: 'svg', 'png' or 'pdf'
|
||||||
|
|
||||||
:param use_thread: if to be run in a separate thread; boolean
|
|
||||||
:param ftype: the type of file for saving the film: 'svg', 'png' or 'pdf'
|
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.app.report_usage("export_positive()")
|
self.app.report_usage("export_positive()")
|
||||||
|
|
|
@ -651,7 +651,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
||||||
}
|
}
|
||||||
|
|
||||||
# #############################################################################
|
# #############################################################################
|
||||||
# ############################ SGINALS ########################################
|
# ############################ SIGNALS ########################################
|
||||||
# #############################################################################
|
# #############################################################################
|
||||||
self.addtool_btn.clicked.connect(self.on_tool_add)
|
self.addtool_btn.clicked.connect(self.on_tool_add)
|
||||||
self.addtool_entry.returnPressed.connect(self.on_tool_add)
|
self.addtool_entry.returnPressed.connect(self.on_tool_add)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
from flatcamTools.ToolCalculators import ToolCalculator
|
from flatcamTools.ToolCalculators import ToolCalculator
|
||||||
from flatcamTools.ToolCalibration import ToolCalibration
|
from flatcamTools.ToolCalibration import ToolCalibration
|
||||||
from flatcamTools.ToolCutOut import CutOut
|
from flatcamTools.ToolCutOut import CutOut
|
||||||
|
|
||||||
from flatcamTools.ToolDblSided import DblSidedTool
|
from flatcamTools.ToolDblSided import DblSidedTool
|
||||||
|
from flatcamTools.ToolExtractDrills import ToolExtractDrills
|
||||||
|
from flatcamTools.ToolAlignObjects import AlignObjects
|
||||||
|
|
||||||
from flatcamTools.ToolFilm import Film
|
from flatcamTools.ToolFilm import Film
|
||||||
|
|
||||||
|
@ -17,10 +18,10 @@ from flatcamTools.ToolDistanceMin import DistanceMin
|
||||||
from flatcamTools.ToolMove import ToolMove
|
from flatcamTools.ToolMove import ToolMove
|
||||||
|
|
||||||
from flatcamTools.ToolNonCopperClear import NonCopperClear
|
from flatcamTools.ToolNonCopperClear import NonCopperClear
|
||||||
|
from flatcamTools.ToolPaint import ToolPaint
|
||||||
|
|
||||||
from flatcamTools.ToolOptimal import ToolOptimal
|
from flatcamTools.ToolOptimal import ToolOptimal
|
||||||
|
|
||||||
from flatcamTools.ToolPaint import ToolPaint
|
|
||||||
from flatcamTools.ToolPanelize import Panelize
|
from flatcamTools.ToolPanelize import Panelize
|
||||||
from flatcamTools.ToolPcbWizard import PcbWizard
|
from flatcamTools.ToolPcbWizard import PcbWizard
|
||||||
from flatcamTools.ToolPDF import ToolPDF
|
from flatcamTools.ToolPDF import ToolPDF
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 457 B |
Binary file not shown.
After Width: | Height: | Size: 539 B |
Binary file not shown.
After Width: | Height: | Size: 459 B |
Binary file not shown.
After Width: | Height: | Size: 745 B |
Binary file not shown.
After Width: | Height: | Size: 565 B |
Binary file not shown.
After Width: | Height: | Size: 900 B |
Loading…
Reference in New Issue