Merged in Beta (pull request #4)

Beta
This commit is contained in:
Marius Stanciu 2020-01-15 11:31:09 +00:00
commit c9af9008fe
23 changed files with 1839 additions and 153 deletions

View File

@ -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("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
"<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&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (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:

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>SHIFT+J</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>SHIFT+M</strong></td>
<td>&nbsp;%s</td>
@ -1506,6 +1519,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20">&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+A</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+C</strong></td>
<td>&nbsp;%s</td>
@ -1518,6 +1535,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
<td height="20"><strong>ALT+E</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+I</strong></td>
<td>&nbsp;%s</td>
</tr>
<tr height="20">
<td height="20"><strong>ALT+J</strong></td>
<td>&nbsp;%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)

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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:

View File

@ -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()))

View File

@ -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):

View File

@ -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))

View File

@ -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)

View File

@ -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()")

View File

@ -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)

View File

@ -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

BIN
share/align16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

BIN
share/align32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

BIN
share/extract_drill16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

BIN
share/extract_drill32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

BIN
share/locate16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

BIN
share/locate32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B