commit
c9af9008fe
296
FlatCAMApp.py
296
FlatCAMApp.py
|
@ -141,7 +141,7 @@ class App(QtCore.QObject):
|
|||
# ################## Version and VERSION DATE ##############################
|
||||
# ##########################################################################
|
||||
version = 8.992
|
||||
version_date = "2020/01/02"
|
||||
version_date = "2020/01/20"
|
||||
beta = True
|
||||
engine = '3D'
|
||||
|
||||
|
@ -240,6 +240,9 @@ class App(QtCore.QObject):
|
|||
# signal emitted when jumping
|
||||
jump_signal = pyqtSignal(tuple)
|
||||
|
||||
# signal emitted when jumping
|
||||
locate_signal = pyqtSignal(tuple, str)
|
||||
|
||||
# close app signal
|
||||
close_app_signal = pyqtSignal()
|
||||
|
||||
|
@ -429,6 +432,7 @@ class App(QtCore.QObject):
|
|||
"global_stats": dict(),
|
||||
"global_tabs_detachable": True,
|
||||
"global_jump_ref": 'abs',
|
||||
"global_locate_pt": 'bl',
|
||||
"global_tpdf_tmargin": 15.0,
|
||||
"global_tpdf_bmargin": 10.0,
|
||||
"global_tpdf_lmargin": 20.0,
|
||||
|
@ -524,8 +528,8 @@ class App(QtCore.QObject):
|
|||
"global_cursor_type": "small",
|
||||
"global_cursor_size": 20,
|
||||
"global_cursor_width": 2,
|
||||
"global_cursor_color": '#000000',
|
||||
"global_cursor_color_enabled": False,
|
||||
"global_cursor_color": '#FF0000',
|
||||
"global_cursor_color_enabled": True,
|
||||
|
||||
# Gerber General
|
||||
"gerber_plot": True,
|
||||
|
@ -954,6 +958,24 @@ class App(QtCore.QObject):
|
|||
"tools_cal_toolchange_xy": '',
|
||||
"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
|
||||
# file associations
|
||||
"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_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
|
||||
# File associations
|
||||
"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.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.menueditselectall.triggered.connect(self.on_selectall)
|
||||
|
@ -2464,12 +2502,14 @@ class App(QtCore.QObject):
|
|||
self.qrcode_tool = None
|
||||
self.copper_thieving_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
|
||||
try:
|
||||
self.install_tools()
|
||||
except AttributeError:
|
||||
pass
|
||||
except AttributeError as e:
|
||||
log.debug("App.__init__() install tools() --> %s" % str(e))
|
||||
|
||||
# ##################################################################################
|
||||
# ########################### SETUP RECENT ITEMS ###################################
|
||||
|
@ -3017,13 +3057,6 @@ class App(QtCore.QObject):
|
|||
|
||||
: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.install(icon=QtGui.QIcon(self.resource_location + '/distance16.png'), pos=self.ui.menuedit,
|
||||
before=self.ui.menueditorigin,
|
||||
|
@ -3035,6 +3068,20 @@ class App(QtCore.QObject):
|
|||
before=self.ui.menueditorigin,
|
||||
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.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.origin_btn.triggered.connect(self.on_set_origin)
|
||||
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.new_script_btn.triggered.connect(self.on_filenewscript)
|
||||
|
@ -3208,6 +3256,9 @@ class App(QtCore.QObject):
|
|||
# Tools Toolbar Signals
|
||||
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.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.ncc_btn.triggered.connect(lambda: self.ncclear_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['ymax'] = ymax
|
||||
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"
|
||||
|
||||
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
|
||||
self.myKeywords.append(obj.options['name'])
|
||||
|
||||
|
@ -7140,15 +7202,13 @@ class App(QtCore.QObject):
|
|||
obj.options['ymin'] = b
|
||||
obj.options['xmax'] = c
|
||||
obj.options['ymax'] = d
|
||||
self.inform.emit('[success] %s...' %
|
||||
_('Origin set'))
|
||||
self.inform.emit('[success] %s...' % _('Origin set'))
|
||||
if noplot_sig is False:
|
||||
self.replot_signal.emit([])
|
||||
|
||||
if location is not None:
|
||||
if len(location) != 2:
|
||||
self.inform.emit('[ERROR_NOTCL] %s...' %
|
||||
_("Origin coordinates specified but incomplete."))
|
||||
self.inform.emit('[ERROR_NOTCL] %s...' % _("Origin coordinates specified but incomplete."))
|
||||
return 'fail'
|
||||
|
||||
x, y = location
|
||||
|
@ -7235,7 +7295,151 @@ class App(QtCore.QObject):
|
|||
|
||||
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:
|
||||
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'],
|
||||
face_color=self.defaults['global_alt_sel_fill'])
|
||||
self.selection_type = False
|
||||
elif dx > 0:
|
||||
elif dx >= 0:
|
||||
self.draw_moving_selection_shape(self.pos, pos)
|
||||
self.selection_type = True
|
||||
else:
|
||||
|
@ -8862,6 +9066,7 @@ class App(QtCore.QObject):
|
|||
pt4 = (float(sel_obj.options['xmin']), float(sel_obj.options['ymax']))
|
||||
|
||||
sel_rect = Polygon([pt1, pt2, pt3, pt4])
|
||||
|
||||
if self.defaults['units'].upper() == 'MM':
|
||||
sel_rect = sel_rect.buffer(-0.1)
|
||||
sel_rect = sel_rect.buffer(0.2)
|
||||
|
@ -10378,7 +10583,8 @@ class App(QtCore.QObject):
|
|||
self.report_usage("export_svg()")
|
||||
|
||||
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()")
|
||||
|
||||
|
@ -10446,7 +10652,8 @@ class App(QtCore.QObject):
|
|||
self.report_usage("save source file()")
|
||||
|
||||
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()")
|
||||
|
||||
|
@ -10489,7 +10696,10 @@ class App(QtCore.QObject):
|
|||
self.report_usage("export_excellon()")
|
||||
|
||||
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()")
|
||||
|
||||
|
@ -10645,7 +10855,8 @@ class App(QtCore.QObject):
|
|||
self.report_usage("export_gerber()")
|
||||
|
||||
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()")
|
||||
|
||||
|
@ -10781,7 +10992,8 @@ class App(QtCore.QObject):
|
|||
self.report_usage("export_dxf()")
|
||||
|
||||
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()")
|
||||
|
||||
|
@ -11983,8 +12195,13 @@ class App(QtCore.QObject):
|
|||
plot_container = container
|
||||
else:
|
||||
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:
|
||||
self.plotcanvas = PlotCanvas(plot_container, self)
|
||||
except Exception as er:
|
||||
|
@ -11997,13 +12214,9 @@ class App(QtCore.QObject):
|
|||
msg += msg_txt
|
||||
self.inform.emit(msg)
|
||||
return 'fail'
|
||||
else:
|
||||
self.plotcanvas = PlotCanvasLegacy(plot_container, self)
|
||||
print("step_2")
|
||||
|
||||
# So it can receive key presses
|
||||
self.plotcanvas.native.setFocus()
|
||||
print("step_3")
|
||||
|
||||
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)
|
||||
|
@ -12012,28 +12225,22 @@ class App(QtCore.QObject):
|
|||
|
||||
# Keys over plot enabled
|
||||
self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
|
||||
print("step_4")
|
||||
|
||||
if self.defaults['global_cursor_type'] == 'small':
|
||||
self.app_cursor = self.plotcanvas.new_cursor()
|
||||
else:
|
||||
self.app_cursor = self.plotcanvas.new_cursor(big=True)
|
||||
|
||||
print("step_5")
|
||||
|
||||
if self.ui.grid_snap_btn.isChecked():
|
||||
self.app_cursor.enabled = True
|
||||
else:
|
||||
self.app_cursor.enabled = False
|
||||
|
||||
print("step_6")
|
||||
|
||||
if self.is_legacy is False:
|
||||
self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
|
||||
else:
|
||||
# will use the default Matplotlib axes
|
||||
self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
|
||||
print("step_7")
|
||||
|
||||
def on_zoom_fit(self, event):
|
||||
"""
|
||||
|
@ -12262,19 +12469,12 @@ class App(QtCore.QObject):
|
|||
new_line_color = color_variant(new_color[:7], 0.7)
|
||||
|
||||
for sel_obj in sel_obj_list:
|
||||
if self.is_legacy is False:
|
||||
sel_obj.fill_color = new_color
|
||||
sel_obj.outline_color = new_line_color
|
||||
sel_obj.fill_color = new_color
|
||||
sel_obj.outline_color = new_line_color
|
||||
|
||||
sel_obj.shapes.redraw(
|
||||
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)
|
||||
)
|
||||
sel_obj.shapes.redraw(
|
||||
update_colors=(new_color, new_line_color)
|
||||
)
|
||||
|
||||
def on_grid_snap_triggered(self, state):
|
||||
if state:
|
||||
|
|
|
@ -1307,7 +1307,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
else:
|
||||
iso_name = outname
|
||||
|
||||
# TODO: This is ugly. Create way to pass data into init function.
|
||||
def iso_init(geo_obj, app_obj):
|
||||
# Propagate options
|
||||
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||
|
@ -1318,8 +1317,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
iso_offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia)
|
||||
|
||||
# if milling type is climb then the move is counter-clockwise around features
|
||||
mill_t = 1 if milling_type == 'cl' else 0
|
||||
geom = self.generate_envelope(iso_offset, mill_t, geometry=work_geo, env_iso_type=iso_t,
|
||||
mill_dir = 1 if milling_type == 'cl' else 0
|
||||
geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
|
||||
follow=follow, nr_passes=i)
|
||||
|
||||
if geom == 'fail':
|
||||
|
@ -1438,7 +1437,6 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
else:
|
||||
iso_name = outname
|
||||
|
||||
# TODO: This is ugly. Create way to pass data into init function.
|
||||
def iso_init(geo_obj, app_obj):
|
||||
# Propagate options
|
||||
geo_obj.options["cnctooldia"] = str(self.options["isotooldia"])
|
||||
|
@ -1448,9 +1446,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||
geo_obj.tool_type = 'C1'
|
||||
|
||||
# if milling type is climb then the move is counter-clockwise around features
|
||||
mill_t = 1 if milling_type == 'cl' else 0
|
||||
mill_t = 1 if milling_type == 'cl' else 0
|
||||
geom = self.generate_envelope(offset, mill_t, geometry=work_geo, env_iso_type=iso_t,
|
||||
mill_dir = 1 if milling_type == 'cl' else 0
|
||||
geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
|
||||
follow=follow,
|
||||
nr_passes=i)
|
||||
|
||||
|
@ -2641,7 +2638,10 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
|
|||
horizontal_header.setDefaultSectionSize(70)
|
||||
horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
|
||||
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(3, QtWidgets.QHeaderView.ResizeToContents)
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
- updates in NCC Tool
|
||||
|
||||
6.01.2019
|
||||
6.01.2020
|
||||
|
||||
- working on new NCC Tool
|
||||
|
||||
|
|
35
camlib.py
35
camlib.py
|
@ -458,8 +458,8 @@ class Geometry(object):
|
|||
"""
|
||||
|
||||
defaults = {
|
||||
"units": 'in',
|
||||
"geo_steps_per_circle": 64
|
||||
"units": 'mm',
|
||||
# "geo_steps_per_circle": 128
|
||||
}
|
||||
|
||||
def __init__(self, geo_steps_per_circle=None):
|
||||
|
@ -528,13 +528,13 @@ class Geometry(object):
|
|||
self.solid_geometry = []
|
||||
|
||||
if type(self.solid_geometry) is list:
|
||||
self.solid_geometry.append(Point(origin).buffer(
|
||||
radius, int(int(self.geo_steps_per_circle) / 4)))
|
||||
self.solid_geometry.append(Point(origin).buffer(radius, int(self.geo_steps_per_circle)))
|
||||
return
|
||||
|
||||
try:
|
||||
self.solid_geometry = self.solid_geometry.union(Point(origin).buffer(
|
||||
radius, int(int(self.geo_steps_per_circle) / 4)))
|
||||
self.solid_geometry = self.solid_geometry.union(
|
||||
Point(origin).buffer(radius, int(self.geo_steps_per_circle))
|
||||
)
|
||||
except Exception as e:
|
||||
log.error("Failed to run union on polygons. %s" % str(e))
|
||||
return
|
||||
|
@ -944,7 +944,7 @@ class Geometry(object):
|
|||
geo_iso.append(pol)
|
||||
else:
|
||||
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
|
||||
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
|
||||
|
||||
|
@ -959,8 +959,7 @@ class Geometry(object):
|
|||
geo_iso.append(working_geo)
|
||||
else:
|
||||
corner_type = 1 if corner is None else corner
|
||||
geo_iso.append(working_geo.buffer(offset, int(int(self.geo_steps_per_circle) / 4),
|
||||
join_style=corner_type))
|
||||
geo_iso.append(working_geo.buffer(offset, int(self.geo_steps_per_circle), join_style=corner_type))
|
||||
|
||||
self.app.proc_container.update_view_text(' %s' % _("Buffering"))
|
||||
geo_iso = unary_union(geo_iso)
|
||||
|
@ -1225,7 +1224,7 @@ class Geometry(object):
|
|||
|
||||
# Can only result in a Polygon or MultiPolygon
|
||||
# NOTE: The resulting polygon can be "empty".
|
||||
current = polygon.buffer((-tooldia / 1.999999), int(int(steps_per_circle) / 4))
|
||||
current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle))
|
||||
if current.area == 0:
|
||||
# Otherwise, trying to to insert current.exterior == None
|
||||
# into the FlatCAMStorage will fail.
|
||||
|
@ -1254,7 +1253,7 @@ class Geometry(object):
|
|||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
# 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:
|
||||
|
||||
# current can be a MultiPolygon
|
||||
|
@ -1372,11 +1371,12 @@ class Geometry(object):
|
|||
|
||||
# Clean inside edges (contours) of the original polygon
|
||||
if contour:
|
||||
outer_edges = [x.exterior for x in autolist(
|
||||
polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle / 4)))]
|
||||
outer_edges = [
|
||||
x.exterior for x in autolist(polygon_to_clear.buffer(-tooldia / 2, int(steps_per_circle)))
|
||||
]
|
||||
inner_edges = []
|
||||
# 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
|
||||
inner_edges.append(y)
|
||||
# geoms += outer_edges + inner_edges
|
||||
|
@ -1626,7 +1626,7 @@ class Geometry(object):
|
|||
# Straight line from current_pt to pt.
|
||||
# Is the toolpath inside the geometry?
|
||||
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:
|
||||
# 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)
|
||||
start = np.arctan2(-gobj['J'], -gobj['I'])
|
||||
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['Y'] = y
|
||||
|
@ -4362,8 +4362,7 @@ class CNCjob(Geometry):
|
|||
visible=visible, layer=1)
|
||||
else:
|
||||
# For Incremental coordinates type G91
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_('G91 coordinates not implemented ...'))
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _('G91 coordinates not implemented ...'))
|
||||
for geo in gcode_parsed:
|
||||
if geo['kind'][0] == 'T':
|
||||
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'))
|
||||
self.menueditjump = self.menuedit.addAction(
|
||||
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
|
||||
self.menuedit.addSeparator()
|
||||
|
@ -825,6 +827,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin'))
|
||||
self.jmp_btn = self.toolbargeo.addAction(
|
||||
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# ###############################
|
||||
|
@ -859,6 +863,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
# ########################################################################
|
||||
self.dblsided_btn = self.toolbartools.addAction(
|
||||
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(
|
||||
QtGui.QIcon(self.app.resource_location + '/cut16_bis.png'), _("Cutout Tool"))
|
||||
self.ncc_btn = self.toolbartools.addAction(
|
||||
|
@ -1474,6 +1483,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
<td height="20"><strong>SHIFT+G</strong></td>
|
||||
<td> %s</td>
|
||||
</tr>
|
||||
<tr height="20">
|
||||
<td height="20"><strong>SHIFT+J</strong></td>
|
||||
<td> %s</td>
|
||||
</tr>
|
||||
<tr height="20">
|
||||
<td height="20"><strong>SHIFT+M</strong></td>
|
||||
<td> %s</td>
|
||||
|
@ -1506,6 +1519,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
<td height="20"> </td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr height="20">
|
||||
<td height="20"><strong>ALT+A</strong></td>
|
||||
<td> %s</td>
|
||||
</tr>
|
||||
<tr height="20">
|
||||
<td height="20"><strong>ALT+C</strong></td>
|
||||
<td> %s</td>
|
||||
|
@ -1518,6 +1535,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
<td height="20"><strong>ALT+E</strong></td>
|
||||
<td> %s</td>
|
||||
</tr>
|
||||
<tr height="20">
|
||||
<td height="20"><strong>ALT+I</strong></td>
|
||||
<td> %s</td>
|
||||
</tr>
|
||||
<tr height="20">
|
||||
<td height="20"><strong>ALT+J</strong></td>
|
||||
<td> %s</td>
|
||||
|
@ -1637,11 +1658,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
|
||||
# SHIFT section
|
||||
_("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"),
|
||||
_("Skew on Y axis"),
|
||||
# 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"),
|
||||
_("Film PCB Tool"), _("Non-Copper Clearing Tool"), _("Optimal 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'))
|
||||
self.jmp_btn = self.toolbargeo.addAction(
|
||||
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(
|
||||
QtGui.QIcon(self.app.resource_location + '/replot32.png'), _("&Replot"))
|
||||
self.clear_plot_btn = self.toolbarview.addAction(
|
||||
|
@ -2470,9 +2497,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
self.zoom_fit_btn = self.toolbarview.addAction(
|
||||
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(
|
||||
QtGui.QIcon(self.app.resource_location + '/shell32.png'), _("&Command Line"))
|
||||
self.new_script_btn = self.toolbarshell.addAction(
|
||||
|
@ -2485,6 +2512,11 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
# ## Tools Toolbar # ##
|
||||
self.dblsided_btn = self.toolbartools.addAction(
|
||||
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(
|
||||
QtGui.QIcon(self.app.resource_location + '/cut16_bis.png'), _("&Cutout Tool"))
|
||||
self.ncc_btn = self.toolbartools.addAction(
|
||||
|
@ -2498,10 +2530,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
self.film_btn = self.toolbartools.addAction(
|
||||
QtGui.QIcon(self.app.resource_location + '/film16.png'), _("Film Tool"))
|
||||
self.solder_btn = self.toolbartools.addAction(
|
||||
QtGui.QIcon(self.app.resource_location + '/solderpastebis32.png'),
|
||||
_("SolderPaste Tool"))
|
||||
QtGui.QIcon(self.app.resource_location + '/solderpastebis32.png'), _("SolderPaste Tool"))
|
||||
self.sub_btn = self.toolbartools.addAction(
|
||||
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()
|
||||
|
||||
|
@ -2834,6 +2869,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
if key == QtCore.Qt.Key_G:
|
||||
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
|
||||
if key == QtCore.Qt.Key_M:
|
||||
self.app.distance_min_tool.run()
|
||||
|
@ -2882,6 +2921,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
if key == Qt.Key_3:
|
||||
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
|
||||
if key == QtCore.Qt.Key_C:
|
||||
self.app.calculator_tool.run(toggle=True)
|
||||
|
@ -2906,6 +2949,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
|
|||
self.app.on_toggle_grid_lines()
|
||||
return
|
||||
|
||||
# Align in Object Tool
|
||||
if key == QtCore.Qt.Key_I:
|
||||
self.app.edrills_tool.run(toggle=True)
|
||||
|
||||
# Fiducials Tool
|
||||
if key == QtCore.Qt.Key_J:
|
||||
self.app.fiducial_tool.run(toggle=True)
|
||||
|
|
|
@ -2009,6 +2009,10 @@ class FCTable(QtWidgets.QTableWidget):
|
|||
palette = QtGui.QPalette()
|
||||
palette.setColor(QtGui.QPalette.Inactive, 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)
|
||||
|
||||
if drag_drop:
|
||||
|
|
|
@ -32,11 +32,11 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
:param container: The parent container in which to draw plots.
|
||||
:rtype: PlotCanvas
|
||||
"""
|
||||
print("step_1_1")
|
||||
|
||||
super(PlotCanvas, self).__init__()
|
||||
# super(PlotCanvas, self).__init__()
|
||||
# QtCore.QObject.__init__(self)
|
||||
# VisPyCanvas.__init__(self)
|
||||
print("step_1_2")
|
||||
super().__init__()
|
||||
|
||||
# VisPyCanvas does not allow new attributes. Override.
|
||||
self.unfreeze()
|
||||
|
@ -46,8 +46,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
# Parent container
|
||||
self.container = container
|
||||
|
||||
print("step_1_3")
|
||||
|
||||
settings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("theme"):
|
||||
theme = settings.value('theme', type=str)
|
||||
|
@ -117,8 +115,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
}
|
||||
)
|
||||
|
||||
print("step_1_4")
|
||||
|
||||
# <VisPyCanvas>
|
||||
self.create_native()
|
||||
self.native.setParent(self.fcapp.ui)
|
||||
|
@ -126,8 +122,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
# <QtCore.QObject>
|
||||
self.container.addWidget(self.native)
|
||||
|
||||
print("step_1_5")
|
||||
|
||||
# ## AXIS # ##
|
||||
self.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 0.8), vertical=True,
|
||||
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,
|
||||
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
|
||||
# all CNC have a limited workspace
|
||||
if self.fcapp.defaults['global_workspace'] is True:
|
||||
self.draw_workspace(workspace_size=self.fcapp.defaults["global_workspaceT"])
|
||||
|
||||
print("step_1_7")
|
||||
|
||||
self.line_parent = None
|
||||
if self.fcapp.defaults["global_cursor_color_enabled"]:
|
||||
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,
|
||||
parent=self.line_parent)
|
||||
|
||||
print("step_1_8")
|
||||
|
||||
self.shape_collections = []
|
||||
|
||||
self.shape_collection = self.new_shape_collection()
|
||||
|
@ -171,10 +159,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
self.big_cursor = None
|
||||
# Keep VisPy canvas happy by letting it be "frozen" again.
|
||||
self.freeze()
|
||||
print("step_1_9")
|
||||
|
||||
self.fit_view()
|
||||
print("step_1_10")
|
||||
|
||||
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.setMinimumWidth(220)
|
||||
|
||||
self.tools2_edrills_group = Tools2EDrillsPrefGroupUI(decimals=self.decimals)
|
||||
self.tools2_edrills_group.setMinimumWidth(220)
|
||||
|
||||
self.vlay = QtWidgets.QVBoxLayout()
|
||||
self.vlay.addWidget(self.tools2_checkrules_group)
|
||||
self.vlay.addWidget(self.tools2_optimal_group)
|
||||
|
||||
self.vlay1 = QtWidgets.QVBoxLayout()
|
||||
self.vlay1.addWidget(self.tools2_qrcode_group)
|
||||
self.vlay1.addWidget(self.tools2_fiducials_group)
|
||||
|
||||
self.vlay2 = QtWidgets.QVBoxLayout()
|
||||
self.vlay2.addWidget(self.tools2_cfill_group)
|
||||
|
||||
self.vlay3 = QtWidgets.QVBoxLayout()
|
||||
self.vlay3.addWidget(self.tools2_fiducials_group)
|
||||
self.vlay3.addWidget(self.tools2_cal_group)
|
||||
self.vlay3.addWidget(self.tools2_edrills_group)
|
||||
|
||||
self.layout.addLayout(self.vlay)
|
||||
self.layout.addLayout(self.vlay1)
|
||||
|
@ -333,7 +337,8 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
|||
# Theme selection
|
||||
self.theme_label = QtWidgets.QLabel('%s:' % _('Theme'))
|
||||
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([
|
||||
|
@ -356,6 +361,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI):
|
|||
self.theme_button = FCButton(_("Apply Theme"))
|
||||
self.theme_button.setToolTip(
|
||||
_("Select a theme for FlatCAM.\n"
|
||||
"It will theme the plot area.\n"
|
||||
"The application will restart after change.")
|
||||
)
|
||||
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.")
|
||||
)
|
||||
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)
|
||||
|
||||
grid0.addWidget(self.worker_number_label, 25, 0)
|
||||
|
@ -1604,21 +1602,13 @@ class GeneralAppPrefGroupUI(OptionsGroupUI):
|
|||
tol_label = QtWidgets.QLabel('%s:' % _("Geo Tolerance"))
|
||||
tol_label.setToolTip(_(
|
||||
"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"
|
||||
"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 = 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.set_precision(6)
|
||||
|
||||
|
@ -7632,6 +7622,218 @@ class Tools2CalPrefGroupUI(OptionsGroupUI):
|
|||
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):
|
||||
def __init__(self, decimals=4, parent=None):
|
||||
# OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None)
|
||||
|
|
|
@ -24,7 +24,8 @@ black = Color("#000000")
|
|||
class VisPyCanvas(scene.SceneCanvas):
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
@ -595,6 +595,7 @@ class Gerber(Geometry):
|
|||
match = self.units_re.search(gline)
|
||||
if match:
|
||||
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)
|
||||
# Changed for issue #80
|
||||
# self.convert_units({'0': 'IN', '1': 'MM'}[match.group(1)])
|
||||
|
@ -834,7 +835,8 @@ class Gerber(Geometry):
|
|||
# --- Buffered ---
|
||||
geo_dict = dict()
|
||||
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) # Sprint Layout Gerbers with ground fill are crashed with above
|
||||
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."))
|
||||
return
|
||||
|
||||
drills = []
|
||||
drills = list()
|
||||
|
||||
for hole in holes:
|
||||
point = Point(hole)
|
||||
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
|
||||
drills.append({"point": point, "tool": "1"})
|
||||
drills.append({"point": point_mirror, "tool": "1"})
|
||||
if 'solid_geometry' not in tools:
|
||||
tools["1"]['solid_geometry'] = []
|
||||
if 'solid_geometry' not in tools["1"]:
|
||||
tools["1"]['solid_geometry'] = list()
|
||||
else:
|
||||
tools["1"]['solid_geometry'].append(point)
|
||||
tools["1"]['solid_geometry'].append(point_mirror)
|
||||
|
||||
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_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
|
||||
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
pass
|
||||
if dx != 0.0:
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
|
||||
self.app.ui.rel_position_label.setText(
|
||||
|
@ -424,11 +425,13 @@ class Distance(FlatCAMTool):
|
|||
if len(self.points) == 1:
|
||||
self.utility_geometry(pos=pos)
|
||||
# and display the temporary angle
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
pass
|
||||
if dx != 0.0:
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
log.debug("Distance.on_mouse_move_meas() -> update utility geometry -> %s" % str(e))
|
||||
pass
|
||||
|
||||
except Exception as 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_reference=skew_reference,
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -1080,23 +1080,28 @@ class Film(FlatCAMTool):
|
|||
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,
|
||||
use_thread=True, ftype='svg'):
|
||||
|
||||
"""
|
||||
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 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 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 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_factor_x: factor to scale the svg geometry on the X axis
|
||||
:param scale_factor_y: factor to scale the svg geometry on the Y axis
|
||||
:param skew_factor_x: factor to skew the svg geometry on the X axis
|
||||
:param skew_factor_y: factor to skew the svg geometry on the Y axis
|
||||
:param skew_reference: reference to use for skew. Can be 'bottomleft', 'bottomright', 'topleft', '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 scale_factor_x: factor to scale the geometry on the X axis
|
||||
:param scale_factor_y: factor to scale the geometry on the Y axis
|
||||
:param skew_factor_x: factor to skew the geometry on the X 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 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 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:
|
||||
"""
|
||||
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_entry.returnPressed.connect(self.on_tool_add)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import sys
|
||||
|
||||
|
||||
from flatcamTools.ToolCalculators import ToolCalculator
|
||||
from flatcamTools.ToolCalibration import ToolCalibration
|
||||
from flatcamTools.ToolCutOut import CutOut
|
||||
|
||||
from flatcamTools.ToolDblSided import DblSidedTool
|
||||
from flatcamTools.ToolExtractDrills import ToolExtractDrills
|
||||
from flatcamTools.ToolAlignObjects import AlignObjects
|
||||
|
||||
from flatcamTools.ToolFilm import Film
|
||||
|
||||
|
@ -17,10 +18,10 @@ from flatcamTools.ToolDistanceMin import DistanceMin
|
|||
from flatcamTools.ToolMove import ToolMove
|
||||
|
||||
from flatcamTools.ToolNonCopperClear import NonCopperClear
|
||||
from flatcamTools.ToolPaint import ToolPaint
|
||||
|
||||
from flatcamTools.ToolOptimal import ToolOptimal
|
||||
|
||||
from flatcamTools.ToolPaint import ToolPaint
|
||||
from flatcamTools.ToolPanelize import Panelize
|
||||
from flatcamTools.ToolPcbWizard import PcbWizard
|
||||
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