From 2ff9cd2439691ba4f2143ba65674ec97770b6b2f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 10 Jan 2020 13:00:47 +0200 Subject: [PATCH 01/20] - print() debugging --- FlatCAMApp.py | 10 +--------- flatcamGUI/PlotCanvas.py | 21 +++------------------ flatcamGUI/VisPyCanvas.py | 22 +++++++++++++++++++++- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 36ee019d..e41567d2 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -11983,7 +11983,7 @@ class App(QtCore.QObject): plot_container = container else: plot_container = self.ui.right_layout - print("step_1") + if self.is_legacy is False: try: self.plotcanvas = PlotCanvas(plot_container, self) @@ -11999,11 +11999,9 @@ class App(QtCore.QObject): 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 +12010,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): """ diff --git a/flatcamGUI/PlotCanvas.py b/flatcamGUI/PlotCanvas.py index a2db822a..b2fd945a 100644 --- a/flatcamGUI/PlotCanvas.py +++ b/flatcamGUI/PlotCanvas.py @@ -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") - # self.create_native() self.native.setParent(self.fcapp.ui) @@ -126,8 +122,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): # 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) diff --git a/flatcamGUI/VisPyCanvas.py b/flatcamGUI/VisPyCanvas.py index cc9aab7d..f7a9c552 100644 --- a/flatcamGUI/VisPyCanvas.py +++ b/flatcamGUI/VisPyCanvas.py @@ -24,15 +24,24 @@ black = Color("#000000") class VisPyCanvas(scene.SceneCanvas): def __init__(self, config=None): - scene.SceneCanvas.__init__(self, keys=None, config=config) + print("vp_1") + try: + # scene.SceneCanvas.__init__(self, keys=None, config=config) + super().__init__(config=config, keys=None) + except Exception as e: + print("VisPyCanvas.__init__() -> %s" % str(e)) + + print("vp_2") self.unfreeze() + print("vp_3") settings = QSettings("Open Source", "FlatCAM") if settings.contains("axis_font_size"): a_fsize = settings.value('axis_font_size', type=int) else: a_fsize = 8 + print("vp_4") if settings.contains("theme"): theme = settings.value('theme', type=str) @@ -50,6 +59,8 @@ class VisPyCanvas(scene.SceneCanvas): # back_color = Color('#272822') # darker # back_color = Color('#3c3f41') # lighter + print("vp_5") + self.central_widget.bgcolor = back_color self.central_widget.border_color = back_color @@ -59,6 +70,8 @@ class VisPyCanvas(scene.SceneCanvas): top_padding = self.grid_widget.add_widget(row=0, col=0, col_span=2) top_padding.height_max = 0 + print("vp_6") + self.yaxis = scene.AxisWidget( orientation='left', axis_color=tick_color, text_color=tick_color, font_size=a_fsize, axis_width=1 ) @@ -76,9 +89,13 @@ class VisPyCanvas(scene.SceneCanvas): # right_padding.width_max = 24 right_padding.width_max = 0 + print("vp_7") + view = self.grid_widget.add_view(row=1, col=1, border_color=tick_color, bgcolor=theme_color) view.camera = Camera(aspect=1, rect=(-25, -25, 150, 150)) + print("vp_8") + # Following function was removed from 'prepare_draw()' of 'Grid' class by patch, # it is necessary to call manually self.grid_widget._update_child_widget_dim() @@ -101,7 +118,10 @@ class VisPyCanvas(scene.SceneCanvas): else: self.grid = scene.GridLines(parent=self.view.scene, color='#dededeff') + print("vp_9") + self.grid.set_gl_state(depth_test=False) + print("vp_10") self.freeze() From 7d0a792085137f3db6158f41e77eb93c941d3fd3 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 10 Jan 2020 14:55:32 +0200 Subject: [PATCH 02/20] - small changes --- FlatCAMApp.py | 3 ++- flatcamGUI/PreferencesUI.py | 22 ++++------------------ flatcamGUI/VisPyCanvas.py | 23 ++--------------------- 3 files changed, 8 insertions(+), 40 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index e41567d2..6d26bfe3 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -8465,7 +8465,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 +8862,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) diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index 365fd17e..b84a514b 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -333,7 +333,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 +357,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 +1589,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 +1598,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) diff --git a/flatcamGUI/VisPyCanvas.py b/flatcamGUI/VisPyCanvas.py index f7a9c552..7d7efe13 100644 --- a/flatcamGUI/VisPyCanvas.py +++ b/flatcamGUI/VisPyCanvas.py @@ -24,24 +24,16 @@ black = Color("#000000") class VisPyCanvas(scene.SceneCanvas): def __init__(self, config=None): - print("vp_1") - try: - # scene.SceneCanvas.__init__(self, keys=None, config=config) - super().__init__(config=config, keys=None) - except Exception as e: - print("VisPyCanvas.__init__() -> %s" % str(e)) - - print("vp_2") + # scene.SceneCanvas.__init__(self, keys=None, config=config) + super().__init__(config=config, keys=None) self.unfreeze() - print("vp_3") settings = QSettings("Open Source", "FlatCAM") if settings.contains("axis_font_size"): a_fsize = settings.value('axis_font_size', type=int) else: a_fsize = 8 - print("vp_4") if settings.contains("theme"): theme = settings.value('theme', type=str) @@ -59,8 +51,6 @@ class VisPyCanvas(scene.SceneCanvas): # back_color = Color('#272822') # darker # back_color = Color('#3c3f41') # lighter - print("vp_5") - self.central_widget.bgcolor = back_color self.central_widget.border_color = back_color @@ -70,8 +60,6 @@ class VisPyCanvas(scene.SceneCanvas): top_padding = self.grid_widget.add_widget(row=0, col=0, col_span=2) top_padding.height_max = 0 - print("vp_6") - self.yaxis = scene.AxisWidget( orientation='left', axis_color=tick_color, text_color=tick_color, font_size=a_fsize, axis_width=1 ) @@ -89,13 +77,9 @@ class VisPyCanvas(scene.SceneCanvas): # right_padding.width_max = 24 right_padding.width_max = 0 - print("vp_7") - view = self.grid_widget.add_view(row=1, col=1, border_color=tick_color, bgcolor=theme_color) view.camera = Camera(aspect=1, rect=(-25, -25, 150, 150)) - print("vp_8") - # Following function was removed from 'prepare_draw()' of 'Grid' class by patch, # it is necessary to call manually self.grid_widget._update_child_widget_dim() @@ -118,10 +102,7 @@ class VisPyCanvas(scene.SceneCanvas): else: self.grid = scene.GridLines(parent=self.view.scene, color='#dededeff') - print("vp_9") - self.grid.set_gl_state(depth_test=False) - print("vp_10") self.freeze() From fc31bb573d8af5d194e1f2db32d376711897b354 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 10 Jan 2020 15:56:23 +0200 Subject: [PATCH 03/20] - working on a new tool: Extract Drills Tool who will create a Excellon object out of the apertures of a Gerber object --- README.md | 4 + flatcamTools/ToolExtractDrills.py | 264 ++++++++++++++++++++++++++++++ flatcamTools/__init__.py | 5 +- 3 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 flatcamTools/ToolExtractDrills.py diff --git a/README.md b/README.md index db7ea631..454d3bac 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +10.02.2019 + +- working on a new tool: Extract Drills Tool who will create a Excellon object out of the apertures of a Gerber object + 8.01.2019 - working in NCC Tool diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py new file mode 100644 index 00000000..be2002a8 --- /dev/null +++ b/flatcamTools/ToolExtractDrills.py @@ -0,0 +1,264 @@ + +from PyQt5 import QtWidgets, QtCore + +from FlatCAMTool import FlatCAMTool +from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry +from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry + +from numpy import Inf + +from shapely.geometry import Point +from shapely import affinity + +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("%s:" % _("GERBER")) + self.grb_label.setToolTip('%s.' % _("Gerber from which to extract drill holes")) + + self.mirror_gerber_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.mirror_gerber_button.setMinimumWidth(60) + + # grid_lay.addRow("Bottom Layer:", self.object_combo) + grid_lay.addWidget(self.grb_label, 0, 0) + grid_lay.addWidget(self.gerber_object_combo, 1, 0) + + # ## Grid Layout + grid_lay1 = QtWidgets.QGridLayout() + self.layout.addLayout(grid_lay1) + + # ## Axis + self.hole_size_radio = RadioSet([{'label': _("Fixed"), 'value': 'fixed'}, + {'label': _("Proportional"), 'value': 'prop'}]) + self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size")) + self.hole_size_label.setToolTip( + _("The type of hole size. Can be:\n" + "- Fixed -> all holes will have a set size\n" + "- Proprotional -> each hole will havea a variable size\n" + "such as to preserve a set annular ring")) + + grid_lay1.addWidget(self.hole_size_label, 3, 0) + grid_lay1.addWidget(self.hole_size_radio, 3, 1) + + self.layout.addWidget(QtWidgets.QLabel('')) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.layout.addWidget(separator_line) + + grid1 = QtWidgets.QGridLayout() + self.layout.addLayout(grid1) + grid1.setColumnStretch(0, 0) + grid1.setColumnStretch(1, 1) + + # 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:' % _("Diameter")) + self.dia_label.setToolTip( + _("Fixed hole diameter.") + ) + + grid1.addWidget(self.dia_label, 1, 0) + grid1.addWidget(self.dia_entry, 1, 1) + + # Annular Ring value + self.ring_entry = FCDoubleSpinner() + self.ring_entry.set_precision(self.decimals) + self.ring_entry.set_range(0.0000, 9999.9999) + + self.ring_label = QtWidgets.QLabel('%s:' % _("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.") + ) + + grid1.addWidget(self.ring_label, 2, 0) + grid1.addWidget(self.ring_entry, 2, 1) + + # Calculate Bounding box + 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) + + # ## Signals + self.hole_size_radio.activated_custom(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.tools = list() + self.drills = dict() + + def install(self, icon=None, separator=None, **kwargs): + FlatCAMTool.install(self, icon, separator, shortcut='ALT+D', **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.ring_entry.set_value(float(self.app.defaults["tools_edrills_hole_ring"])) + + def on_extract_drills_click(self): + selection_index = self.gerber_object_combo.currentIndex() + # fcobj = self.app.collection.object_list[selection_index] + model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex()) + try: + fcobj = model_index.internalPointer().obj + except Exception as e: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) + return + + if not isinstance(fcobj, FlatCAMGerber): + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored.")) + return + + axis = self.mirror_axis.get_value() + mode = self.axis_location.get_value() + + if mode == "point": + try: + px, py = self.point_entry.get_value() + except TypeError: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' coordinates missing. " + "Using Origin (0, 0) as mirroring reference.")) + px, py = (0, 0) + + else: + selection_index_box = self.box_combo.currentIndex() + model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex()) + try: + bb_obj = model_index_box.internalPointer().obj + except Exception as e: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ...")) + return + + xmin, ymin, xmax, ymax = bb_obj.bounds() + px = 0.5 * (xmin + xmax) + py = 0.5 * (ymin + ymax) + + fcobj.mirror(axis, [px, py]) + self.app.object_changed.emit(fcobj) + fcobj.plot() + self.app.inform.emit('[success] Gerber %s %s...' % (str(fcobj.options['name']), _("was mirrored"))) + + def on_hole_size_toggle(self, val): + if val == "fixed": + self.dia_entry.show() + self.dia_label.show() + + self.ring_label.hide() + self.ring_entry.hide() + else: + self.dia_entry.hide() + self.dia_label.hide() + + self.ring_label.show() + self.ring_entry.show() + + def reset_fields(self): + self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.gerber_object_combo.setCurrentIndex(0) diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py index d986974e..be389e70 100644 --- a/flatcamTools/__init__.py +++ b/flatcamTools/__init__.py @@ -1,6 +1,5 @@ import sys - from flatcamTools.ToolCalculators import ToolCalculator from flatcamTools.ToolCalibration import ToolCalibration from flatcamTools.ToolCutOut import CutOut @@ -17,10 +16,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 @@ -32,6 +31,8 @@ from flatcamTools.ToolRulesCheck import RulesCheck from flatcamTools.ToolCopperThieving import ToolCopperThieving from flatcamTools.ToolFiducials import ToolFiducials +from flatcamTools.ToolExtractDrills import ToolExtractDrills + from flatcamTools.ToolShell import FCShell from flatcamTools.ToolSolderPaste import SolderPaste from flatcamTools.ToolSub import ToolSub From f2ccb48c98ada313ce1eccb515fde86f39f2d04a Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 10 Jan 2020 16:56:29 +0200 Subject: [PATCH 04/20] - finished the GUI in the Extract Drills Tool --- FlatCAMApp.py | 28 +++++--- README.md | 1 + flatcamTools/ToolExtractDrills.py | 115 +++++++++++++----------------- 3 files changed, 70 insertions(+), 74 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 6d26bfe3..3815287c 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -954,6 +954,11 @@ 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_ring": 0.2, + # Utilities # file associations "fa_excellon": 'drd, drl, exc, ncd, tap, xln', @@ -2464,12 +2469,13 @@ class App(QtCore.QObject): self.qrcode_tool = None self.copper_thieving_tool = None self.fiducial_tool = None + self.edrills_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 +3023,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 +3034,17 @@ 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.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')) diff --git a/README.md b/README.md index 454d3bac..9093667b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing. 10.02.2019 - 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 8.01.2019 diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index be2002a8..7f47ab3e 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -59,21 +59,15 @@ class ToolExtractDrills(FlatCAMTool): self.grb_label = QtWidgets.QLabel("%s:" % _("GERBER")) self.grb_label.setToolTip('%s.' % _("Gerber from which to extract drill holes")) - self.mirror_gerber_button.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.mirror_gerber_button.setMinimumWidth(60) - # grid_lay.addRow("Bottom Layer:", self.object_combo) grid_lay.addWidget(self.grb_label, 0, 0) grid_lay.addWidget(self.gerber_object_combo, 1, 0) # ## Grid Layout - grid_lay1 = QtWidgets.QGridLayout() - self.layout.addLayout(grid_lay1) + grid1 = QtWidgets.QGridLayout() + self.layout.addLayout(grid1) + grid1.setColumnStretch(0, 0) + grid1.setColumnStretch(1, 1) # ## Axis self.hole_size_radio = RadioSet([{'label': _("Fixed"), 'value': 'fixed'}, @@ -85,20 +79,15 @@ class ToolExtractDrills(FlatCAMTool): "- Proprotional -> each hole will havea a variable size\n" "such as to preserve a set annular ring")) - grid_lay1.addWidget(self.hole_size_label, 3, 0) - grid_lay1.addWidget(self.hole_size_radio, 3, 1) + grid1.addWidget(self.hole_size_label, 3, 0) + grid1.addWidget(self.hole_size_radio, 3, 1) - self.layout.addWidget(QtWidgets.QLabel('')) + # grid_lay1.addWidget(QtWidgets.QLabel('')) separator_line = QtWidgets.QFrame() separator_line.setFrameShape(QtWidgets.QFrame.HLine) separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.layout.addWidget(separator_line) - - grid1 = QtWidgets.QGridLayout() - self.layout.addLayout(grid1) - grid1.setColumnStretch(0, 0) - grid1.setColumnStretch(1, 1) + grid1.addWidget(separator_line, 5, 0, 1, 2) # Diameter value self.dia_entry = FCDoubleSpinner() @@ -110,8 +99,8 @@ class ToolExtractDrills(FlatCAMTool): _("Fixed hole diameter.") ) - grid1.addWidget(self.dia_label, 1, 0) - grid1.addWidget(self.dia_entry, 1, 1) + grid1.addWidget(self.dia_label, 7, 0) + grid1.addWidget(self.dia_entry, 7, 1) # Annular Ring value self.ring_entry = FCDoubleSpinner() @@ -125,8 +114,8 @@ class ToolExtractDrills(FlatCAMTool): "and the margin of the copper pad.") ) - grid1.addWidget(self.ring_label, 2, 0) - grid1.addWidget(self.ring_entry, 2, 1) + grid1.addWidget(self.ring_label, 8, 0) + grid1.addWidget(self.ring_entry, 8, 1) # Calculate Bounding box self.e_drills_button = QtWidgets.QPushButton(_("Extract Drills")) @@ -157,7 +146,7 @@ class ToolExtractDrills(FlatCAMTool): self.layout.addWidget(self.reset_button) # ## Signals - self.hole_size_radio.activated_custom(self.on_hole_size_toggle) + 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) @@ -165,7 +154,7 @@ class ToolExtractDrills(FlatCAMTool): self.drills = dict() def install(self, icon=None, separator=None, **kwargs): - FlatCAMTool.install(self, icon, separator, shortcut='ALT+D', **kwargs) + FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs) def run(self, toggle=True): self.app.report_usage("Extract Drills()") @@ -204,60 +193,56 @@ class ToolExtractDrills(FlatCAMTool): def on_extract_drills_click(self): selection_index = self.gerber_object_combo.currentIndex() - # fcobj = self.app.collection.object_list[selection_index] model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex()) + try: fcobj = model_index.internalPointer().obj except Exception as e: self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) return - if not isinstance(fcobj, FlatCAMGerber): - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored.")) - return - - axis = self.mirror_axis.get_value() - mode = self.axis_location.get_value() - - if mode == "point": - try: - px, py = self.point_entry.get_value() - except TypeError: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' coordinates missing. " - "Using Origin (0, 0) as mirroring reference.")) - px, py = (0, 0) - - else: - selection_index_box = self.box_combo.currentIndex() - model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex()) - try: - bb_obj = model_index_box.internalPointer().obj - except Exception as e: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ...")) - return - - xmin, ymin, xmax, ymax = bb_obj.bounds() - px = 0.5 * (xmin + xmax) - py = 0.5 * (ymin + ymax) - - fcobj.mirror(axis, [px, py]) - self.app.object_changed.emit(fcobj) - fcobj.plot() + # axis = self.mirror_axis.get_value() + # mode = self.axis_location.get_value() + # + # if mode == "point": + # try: + # px, py = self.point_entry.get_value() + # except TypeError: + # self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' coordinates missing. " + # "Using Origin (0, 0) as mirroring reference.")) + # px, py = (0, 0) + # + # else: + # selection_index_box = self.box_combo.currentIndex() + # model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex()) + # try: + # bb_obj = model_index_box.internalPointer().obj + # except Exception as e: + # self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ...")) + # return + # + # xmin, ymin, xmax, ymax = bb_obj.bounds() + # px = 0.5 * (xmin + xmax) + # py = 0.5 * (ymin + ymax) + # + # fcobj.mirror(axis, [px, py]) + # self.app.object_changed.emit(fcobj) + # fcobj.plot() self.app.inform.emit('[success] Gerber %s %s...' % (str(fcobj.options['name']), _("was mirrored"))) def on_hole_size_toggle(self, val): if val == "fixed": - self.dia_entry.show() - self.dia_label.show() + self.dia_entry.setDisabled(False) + self.dia_label.setDisabled(False) - self.ring_label.hide() - self.ring_entry.hide() + self.ring_label.setDisabled(True) + self.ring_entry.setDisabled(True) else: - self.dia_entry.hide() - self.dia_label.hide() + self.dia_entry.setDisabled(True) + self.dia_label.setDisabled(True) - self.ring_label.show() - self.ring_entry.show() + self.ring_label.setDisabled(False) + self.ring_entry.setDisabled(False) def reset_fields(self): self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) From c16ecfe0c3de7eb8855da06bf43cc923100604db Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 11 Jan 2020 00:52:06 +0200 Subject: [PATCH 05/20] - 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 --- FlatCAMApp.py | 17 ++++-- README.md | 3 + flatcamTools/ToolDblSided.py | 7 ++- flatcamTools/ToolExtractDrills.py | 98 +++++++++++++++++++++---------- flatcamTools/ToolFilm.py | 31 ++++++---- 5 files changed, 103 insertions(+), 53 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 3815287c..d80e3ec5 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -10389,7 +10389,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()") @@ -10457,7 +10458,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()") @@ -10500,7 +10502,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()") @@ -10656,7 +10661,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()") @@ -10792,7 +10798,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()") diff --git a/README.md b/README.md index 9093667b..3cc30ba8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ CAD program, and create G-Code for Isolation routing. - 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.2019 diff --git a/flatcamTools/ToolDblSided.py b/flatcamTools/ToolDblSided.py index 3308c159..0b00fc6e 100644 --- a/flatcamTools/ToolDblSided.py +++ b/flatcamTools/ToolDblSided.py @@ -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): diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index 7f47ab3e..5d2a604e 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -150,11 +150,8 @@ class ToolExtractDrills(FlatCAMTool): self.e_drills_button.clicked.connect(self.on_extract_drills_click) self.reset_button.clicked.connect(self.set_tool_ui) - self.tools = list() - self.drills = dict() - def install(self, icon=None, separator=None, **kwargs): - FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs) + FlatCAMTool.install(self, icon, separator, shortcut='ALT+I', **kwargs) def run(self, toggle=True): self.app.report_usage("Extract Drills()") @@ -192,6 +189,12 @@ class ToolExtractDrills(FlatCAMTool): self.ring_entry.set_value(float(self.app.defaults["tools_edrills_hole_ring"])) def on_extract_drills_click(self): + + drill_dia = self.dia_entry.get_value() + ring_val = self.ring_entry.get_value() + 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()) @@ -201,34 +204,65 @@ class ToolExtractDrills(FlatCAMTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) return - # axis = self.mirror_axis.get_value() - # mode = self.axis_location.get_value() - # - # if mode == "point": - # try: - # px, py = self.point_entry.get_value() - # except TypeError: - # self.app.inform.emit('[WARNING_NOTCL] %s' % _("'Point' coordinates missing. " - # "Using Origin (0, 0) as mirroring reference.")) - # px, py = (0, 0) - # - # else: - # selection_index_box = self.box_combo.currentIndex() - # model_index_box = self.app.collection.index(selection_index_box, 0, self.box_combo.rootModelIndex()) - # try: - # bb_obj = model_index_box.internalPointer().obj - # except Exception as e: - # self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Box object loaded ...")) - # return - # - # xmin, ymin, xmax, ymax = bb_obj.bounds() - # px = 0.5 * (xmin + xmax) - # py = 0.5 * (ymin + ymax) - # - # fcobj.mirror(axis, [px, py]) - # self.app.object_changed.emit(fcobj) - # fcobj.plot() - self.app.inform.emit('[success] Gerber %s %s...' % (str(fcobj.options['name']), _("was mirrored"))) + 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(): + 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']) + else: + for apid, apid_value in fcobj.apertures.items(): + ap_type = apid_value['type'] + + dia = float(apid_value['size']) - (2 * ring_val) + if ap_type == 'R' or ap_type == 'O': + width = float(apid_value['width']) + height = float(apid_value['height']) + if width >= height: + dia = float(apid_value['height']) - (2 * ring_val) + else: + dia = float(apid_value['width']) - (2 * ring_val) + + tool_in_drills = False + for tool, tool_val in tools.items(): + if abs(float('%.*f' % (self.decimals, tool_val["C"])) - 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']) + + 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": diff --git a/flatcamTools/ToolFilm.py b/flatcamTools/ToolFilm.py index ead110b1..a3542e29 100644 --- a/flatcamTools/ToolFilm.py +++ b/flatcamTools/ToolFilm.py @@ -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()") From c28f08a3927859887b0c1530c7831334441dd494 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 11 Jan 2020 17:30:48 +0200 Subject: [PATCH 06/20] - 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) --- FlatCAMApp.py | 11 +- README.md | 11 +- flatcamGUI/GUIElements.py | 4 + flatcamTools/ToolDistance.py | 23 +-- flatcamTools/ToolExtractDrills.py | 279 +++++++++++++++++++++++++++--- 5 files changed, 287 insertions(+), 41 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index d80e3ec5..05c2a850 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -957,7 +957,16 @@ class App(QtCore.QObject): # Drills Extraction Tool "tools_edrills_hole_type": 'fixed', "tools_edrills_hole_fixed_dia": 0.5, - "tools_edrills_hole_ring": 0.2, + "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, # Utilities # file associations diff --git a/README.md b/README.md index 3cc30ba8..19a3cfa8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,12 @@ CAD program, and create G-Code for Isolation routing. ================================================= -10.02.2019 +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) + +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 @@ -17,14 +22,14 @@ CAD program, and create G-Code for Isolation routing. - finished the Extract Drills Tool - fixed a small issue in the DoubleSided Tool -8.01.2019 +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 diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py index f52aced9..05314ffe 100644 --- a/flatcamGUI/GUIElements.py +++ b/flatcamGUI/GUIElements.py @@ -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: diff --git a/flatcamTools/ToolDistance.py b/flatcamTools/ToolDistance.py index c1f9ed8b..1a5bc568 100644 --- a/flatcamTools/ToolDistance.py +++ b/flatcamTools/ToolDistance.py @@ -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)) diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index 5d2a604e..fa50ecc8 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -2,13 +2,9 @@ from PyQt5 import QtWidgets, QtCore from FlatCAMTool import FlatCAMTool -from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry -from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry - -from numpy import Inf +from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox from shapely.geometry import Point -from shapely import affinity import logging import gettext @@ -60,8 +56,62 @@ class ToolExtractDrills(FlatCAMTool): 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) - grid_lay.addWidget(self.gerber_object_combo, 1, 0) + 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("%s:" % _("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() @@ -102,22 +152,95 @@ class ToolExtractDrills(FlatCAMTool): grid1.addWidget(self.dia_label, 7, 0) grid1.addWidget(self.dia_entry, 7, 1) - # Annular Ring value - self.ring_entry = FCDoubleSpinner() - self.ring_entry.set_precision(self.decimals) - self.ring_entry.set_range(0.0000, 9999.9999) + self.ring_frame = QtWidgets.QFrame() + self.ring_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.ring_frame) - self.ring_label = QtWidgets.QLabel('%s:' % _("Annular Ring")) + 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('%s' % _("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) - grid1.addWidget(self.ring_label, 8, 0) - grid1.addWidget(self.ring_entry, 8, 1) + # 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.") + ) - # Calculate Bounding box + 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) + + # 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.") @@ -145,11 +268,42 @@ class ToolExtractDrills(FlatCAMTool): """) 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) + # ## 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) @@ -186,12 +340,28 @@ class ToolExtractDrills(FlatCAMTool): 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.ring_entry.set_value(float(self.app.defaults["tools_edrills_hole_ring"])) + + 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"]) def on_extract_drills_click(self): drill_dia = self.dia_entry.get_value() - ring_val = self.ring_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() + drills = list() tools = dict() @@ -211,6 +381,29 @@ class ToolExtractDrills(FlatCAMTool): 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"}) @@ -218,22 +411,46 @@ class ToolExtractDrills(FlatCAMTool): 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 else: + drills_found = set() for apid, apid_value in fcobj.apertures.items(): ap_type = apid_value['type'] - dia = float(apid_value['size']) - (2 * ring_val) - if ap_type == 'R' or ap_type == 'O': + dia = None + if ap_type == 'C': + if self.circular_cb.get_value(): + dia = float(apid_value['size']) - (2 * circ_r_val) + elif ap_type == 'R' or ap_type == 'O': width = float(apid_value['width']) height = float(apid_value['height']) - if width >= height: - dia = float(apid_value['height']) - (2 * ring_val) + + # 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: - dia = float(apid_value['width']) - (2 * ring_val) + 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(): + dia = float(apid_value['size']) - (2 * other_r_val) + + # if dia is None then none of the above applied so we skip th e 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"])) - dia) < (10 ** -self.decimals): + 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: @@ -255,6 +472,16 @@ class ToolExtractDrills(FlatCAMTool): 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 @@ -269,14 +496,12 @@ class ToolExtractDrills(FlatCAMTool): self.dia_entry.setDisabled(False) self.dia_label.setDisabled(False) - self.ring_label.setDisabled(True) - self.ring_entry.setDisabled(True) + self.ring_frame.setDisabled(True) else: self.dia_entry.setDisabled(True) self.dia_label.setDisabled(True) - self.ring_label.setDisabled(False) - self.ring_entry.setDisabled(False) + self.ring_frame.setDisabled(False) def reset_fields(self): self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) From f8c22ea32fb1c3dc8d3380afcb103d90ef63496c Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 12 Jan 2020 00:30:17 +0200 Subject: [PATCH 07/20] - Extract Drills Tool: fixed issue with oblong pads and with pads made from aperture macros - Extract Drills Tool: added controls in Edit -> Preferences --- FlatCAMApp.py | 14 +++ README.md | 2 + flatcamGUI/PreferencesUI.py | 190 +++++++++++++++++++++++++++++- flatcamTools/ToolExtractDrills.py | 27 ++++- 4 files changed, 228 insertions(+), 5 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 05c2a850..63504639 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1592,6 +1592,20 @@ 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_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, diff --git a/README.md b/README.md index 19a3cfa8..afbc0e06 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ CAD program, and create G-Code for Isolation routing. - 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 diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index b84a514b..a073b534 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -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) @@ -7618,6 +7622,190 @@ 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('%s:' % _('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("%s:" % _("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"), 'value': 'fixed'}, + {'label': _("Proportional"), 'value': 'prop'}]) + self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size")) + self.hole_size_label.setToolTip( + _("The type of hole size. Can be:\n" + "- Fixed -> all holes will have a set size\n" + "- Proprotional -> each hole will havea a variable size\n" + "such as to preserve a set annular ring")) + + 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) + + # 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:' % _("Diameter")) + self.dia_label.setToolTip( + _("Fixed hole diameter.") + ) + + grid_lay.addWidget(self.dia_label, 11, 0) + grid_lay.addWidget(self.dia_entry, 11, 1) + + # Annular Ring value + self.ring_label = QtWidgets.QLabel('%s' % _("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, 12, 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, 13, 0) + grid_lay.addWidget(self.circular_ring_entry, 13, 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, 14, 0) + grid_lay.addWidget(self.oblong_ring_entry, 14, 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, 15, 0) + grid_lay.addWidget(self.square_ring_entry, 15, 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, 16, 0) + grid_lay.addWidget(self.rectangular_ring_entry, 16, 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, 17, 0) + grid_lay.addWidget(self.other_ring_entry, 17, 1) + + self.layout.addStretch() + + class FAExcPrefGroupUI(OptionsGroupUI): def __init__(self, decimals=4, parent=None): # OptionsGroupUI.__init__(self, "Excellon File associations Preferences", parent=None) diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index fa50ecc8..c0300549 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -59,7 +59,7 @@ class ToolExtractDrills(FlatCAMTool): 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("%s:" % _("Processed Pads Type")) + self.padt_label = QtWidgets.QLabel("%s" % _("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" @@ -424,7 +424,15 @@ class ToolExtractDrills(FlatCAMTool): if ap_type == 'C': if self.circular_cb.get_value(): dia = float(apid_value['size']) - (2 * circ_r_val) - elif ap_type == 'R' or ap_type == 'O': + 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 * rect_r_val) + else: + dia = float(apid_value['width']) - (2 * rect_r_val) + elif ap_type == 'R': width = float(apid_value['width']) height = float(apid_value['height']) @@ -441,9 +449,20 @@ class ToolExtractDrills(FlatCAMTool): dia = float(apid_value['width']) - (2 * rect_r_val) else: if self.other_cb.get_value(): - dia = float(apid_value['size']) - (2 * other_r_val) + 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 th e following + # if dia is None then none of the above applied so we skip the following if dia is None: continue From a9b93cafa17f5cfebb65d9d1e0b5aff2f41d67c1 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 12 Jan 2020 03:28:05 +0200 Subject: [PATCH 08/20] - improved the circle approximation resolution --- FlatCAMObj.py | 11 ++++------- README.md | 6 +++++- camlib.py | 31 +++++++++++++++---------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 712d766a..1ba88825 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -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) diff --git a/README.md b/README.md index afbc0e06..39de865e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +12.01.2020 + +- improved the circle approximation resolution + 11.01.2020 - fixed an issue in the Distance Tool @@ -36,7 +40,7 @@ CAD program, and create G-Code for Isolation routing. - 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 diff --git a/camlib.py b/camlib.py index 1e58a423..6bbaf9ef 100644 --- a/camlib.py +++ b/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): @@ -529,12 +529,12 @@ class Geometry(object): if type(self.solid_geometry) is list: self.solid_geometry.append(Point(origin).buffer( - radius, int(int(self.geo_steps_per_circle) / 4))) + 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))) + 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] From c9111dac9b861d0144e52f45c58d5dc9aef05991 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 12 Jan 2020 04:05:13 +0200 Subject: [PATCH 09/20] - fixed an issue in Gerber parser with detecting old kind of units --- README.md | 1 + camlib.py | 8 ++++---- flatcamParsers/ParseGerber.py | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 39de865e..57224d90 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing. 12.01.2020 - improved the circle approximation resolution +- fixed an issue in Gerber parser with detecting old kind of units 11.01.2020 diff --git a/camlib.py b/camlib.py index 6bbaf9ef..063478b1 100644 --- a/camlib.py +++ b/camlib.py @@ -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(self.geo_steps_per_circle))) + 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(self.geo_steps_per_circle))) + 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 diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index 6f79c31a..7fbfce1f 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -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)]) From 5b3f318e567555f258bb7fab5e48b36dcd3fa854 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 12 Jan 2020 17:26:48 +0200 Subject: [PATCH 10/20] - if CTRL key is pressed during app startup the app will start in the Legacy(2D) graphic engine compatibility mode --- FlatCAMApp.py | 9 ++++++--- README.md | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 63504639..2b25ad8f 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -12025,7 +12025,12 @@ class App(QtCore.QObject): else: plot_container = self.ui.right_layout - 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: @@ -12038,8 +12043,6 @@ class App(QtCore.QObject): msg += msg_txt self.inform.emit(msg) return 'fail' - else: - self.plotcanvas = PlotCanvasLegacy(plot_container, self) # So it can receive key presses self.plotcanvas.native.setFocus() diff --git a/README.md b/README.md index 57224d90..786cbb6b 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ CAD program, and create G-Code for Isolation routing. - 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 From 02cfd9671566718e4d799d9d025d0a7fd2afaa88 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 13 Jan 2020 16:06:29 +0200 Subject: [PATCH 11/20] - fixed a small GUI issue in Excellon UI when Basic mode is active --- FlatCAMApp.py | 4 ++-- FlatCAMObj.py | 5 ++++- README.md | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 2b25ad8f..43e94c63 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -524,8 +524,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, diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 1ba88825..6b98f6f2 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -2638,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) diff --git a/README.md b/README.md index 786cbb6b..a30058fc 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ CAD program, and create G-Code for Isolation routing. ================================================= +13.01.2020 + +- fixed a small GUI issue in Excellon UI when Basic mode is active + 12.01.2020 - improved the circle approximation resolution From 41277d78ce99bf742862a3337c27ed61da139f6a Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 13 Jan 2020 21:43:25 +0200 Subject: [PATCH 12/20] - started the add of a new Tool: Align Objects Tool which will align (sync) objects of Gerber or Excellon type --- FlatCAMApp.py | 4 + README.md | 1 + flatcamTools/ToolAlignObjects.py | 423 ++++++++++++++++++++++++++++++ flatcamTools/ToolExtractDrills.py | 6 + flatcamTools/__init__.py | 4 +- share/align16.png | Bin 0 -> 457 bytes share/align32.png | Bin 0 -> 539 bytes 7 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 flatcamTools/ToolAlignObjects.py create mode 100644 share/align16.png create mode 100644 share/align32.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 43e94c63..ca691c00 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2493,6 +2493,7 @@ class App(QtCore.QObject): 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: @@ -3065,6 +3066,9 @@ class App(QtCore.QObject): 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) diff --git a/README.md b/README.md index a30058fc..414cffad 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ CAD program, and create G-Code for Isolation routing. 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 12.01.2020 diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py new file mode 100644 index 00000000..e787d0c6 --- /dev/null +++ b/flatcamTools/ToolAlignObjects.py @@ -0,0 +1,423 @@ +# ########################################################## +# 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 + +from copy import deepcopy + +import numpy as np + +from shapely.geometry import Point + +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('%s' % _("Selection of the aligned object")) + grid0.addWidget(self.aligned_label, 0, 0, 1, 2) + + # Type of object to be aligned + self.type_obj_combo = QtWidgets.QComboBox() + self.type_obj_combo.addItem("Gerber") + self.type_obj_combo.addItem("Excellon") + self.type_obj_combo.addItem("Geometry") + + 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.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.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, Excellon or Geometry.\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 = QtWidgets.QComboBox() + 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('%s' % _("Selection of the aligner object")) + self.aligned_label.setToolTip( + _("Object to which the other objects will be aligned to (moved).") + ) + grid0.addWidget(self.aligned_label, 6, 0, 1, 2) + + # Type of object to be aligned to = aligner + self.type_aligner_obj_combo = QtWidgets.QComboBox() + self.type_aligner_obj_combo.addItem("Gerber") + self.type_aligner_obj_combo.addItem("Excellon") + self.type_aligner_obj_combo.addItem("Geometry") + + 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.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.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, Excellon or Geometry.\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 = QtWidgets.QComboBox() + 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) + + # 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 + + # here store the alignment points for the aligned object + self.aligned_clicked_points = list() + + # here store the alignment points for the aligner object + self.aligner_clicked_points = list() + + # counter for the clicks + self.click_cnt = 0 + + 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.click_cnt = 0 + + 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): + + 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.object_combo.currentIndex() + aligner_obj_model_index = self.app.collection.index( + aligner_obj_sel_index, 0, self.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 + + # 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.app.inform.emit(_("Get First alignment point on the aligned object.")) + + 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.click_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.click_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: + if not len(self.click_points): + self.reset_calibration_points() + self.disconnect_cal_events() + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request.")) + + def check_points(self): + if len(self.aligned_click_points) == 1: + self.app.inform.emit(_("Get Second alignment point on aligned object. " + "Or right click to get First alignment point on the aligner object.")) + + if len(self.aligned_click_points) == 2: + self.app.inform.emit(_("Get First alignment point on the aligner object.")) + + if len(self.aligner_click_points) == 1: + self.app.inform.emit(_("Get Second alignment point on the aligner object. Or right click to finish.")) + self.align_translate() + self.align_rotate() + self.disconnect_cal_events() + + def align_translate(self): + pass + + def align_rotate(self): + pass + + def execute(self): + aligned_name = self.object_combo.currentText() + + # Get source object. + try: + aligned_obj = self.app.collection.get_by_name(str(aligned_name)) + except Exception as e: + log.debug("AlignObjects.on_align() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), aligned_name)) + return "Could not retrieve object: %s" % aligned_name + + if aligned_obj is None: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), aligned_obj)) + return "Object not found: %s" % aligned_obj + + aligner_name = self.box_combo.currentText() + + try: + aligner_obj = self.app.collection.get_by_name(aligner_name) + except Exception as e: + log.debug("AlignObjects.on_align() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), aligner_name)) + return "Could not retrieve object: %s" % aligner_name + + if aligner_obj is None: + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), aligner_name)) + + def align_job(): + pass + + proc = self.app.proc_container.new(_("Working...")) + + def job_thread(app_obj): + try: + align_job() + app_obj.inform.emit('[success] %s' % _("Panel created successfully.")) + except Exception as ee: + proc.done() + log.debug(str(ee)) + return + proc.done() + + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + + 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.click_cnt = 0 + + 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())) diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index c0300549..e0c72c95 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -1,3 +1,9 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# File Author: Marius Adrian Stanciu (c) # +# Date: 1/10/2020 # +# MIT Licence # +# ########################################################## from PyQt5 import QtWidgets, QtCore diff --git a/flatcamTools/__init__.py b/flatcamTools/__init__.py index be389e70..a593e6c4 100644 --- a/flatcamTools/__init__.py +++ b/flatcamTools/__init__.py @@ -5,6 +5,8 @@ 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 @@ -31,8 +33,6 @@ from flatcamTools.ToolRulesCheck import RulesCheck from flatcamTools.ToolCopperThieving import ToolCopperThieving from flatcamTools.ToolFiducials import ToolFiducials -from flatcamTools.ToolExtractDrills import ToolExtractDrills - from flatcamTools.ToolShell import FCShell from flatcamTools.ToolSolderPaste import SolderPaste from flatcamTools.ToolSub import ToolSub diff --git a/share/align16.png b/share/align16.png new file mode 100644 index 0000000000000000000000000000000000000000..d21f1cec306b4d64a76075be2dd1bcb12a04237c GIT binary patch literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=YDR+uenMVO6iP5s=4O z;1OBOz`%C|gc+x5^GO2**-JcqUD=;7@(N2TujDx{1r(a@>EaloaenDVTdqS10xb`Z z&%Yw(nJ>%2pOn6|DS~TfllljlFtMbW#W!B)uF-8e;uN@Fh3nC69iGDmyS+V>I9tn( zZrH-pns>C|lm5&#I}Zo2BpG(_9Olaw4{jAI$@$WAZ*tL6*r2<1_o9J2D}v-zz{&vkei>9nO2Eg zLyhW$A3zO~ARB`7(@M${i&7cN%ggmL^RkPR6AM!H@{7`Ezq0{_6oaR$pUXO@geCw+ CkCP1m literal 0 HcmV?d00001 diff --git a/share/align32.png b/share/align32.png new file mode 100644 index 0000000000000000000000000000000000000000..b81511a28901c479d7b078e86de71a66c8d92602 GIT binary patch literal 539 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z;^_M8K-LVNdpDhOFVsD*`F}-3QJ1nGsvv~3cd7naSZV|es!uXUz3AC>;47e z3KeYL8a7S#8xO_3Kft=>i;7BE)UB230>YWL&7G~Sy^P#EjlA!?)Kq?LyFIhgIzO#U zyd^{Oja{tXB<6Q_zVhX!lr26S>}4?d#ed$Mh|?T$HedLrnH)80JkI6*`u&DOzjOFJ zEHD4mIH;zidcpg{jp-U)53Q?SrCn91lzCCin&7#Ld;O#dYdub$RXUZ+#%UVpD`2d~ zm~c?0p+!;MI96@pjrJt*oWA=W9g=&vS3Ek_;W59_dj-!Vt5bZ6d2^*q>&1QaFPX_N z*?*KJ-t7aoytkI}46}R3#Cgv-*j-b21Pnd~)e_f;l9a@fRIB8oR3OD*WME{VYhb8r zXdGf_X=P*zMA`-hRt5$KFKOID(U6;;l9^VCTf>I_328tLk{}y`^V3So6N^$A%FE03 gGV`*FlM@S4_413-XTP(N0xDwgboFyt=akR{080n5DgXcg literal 0 HcmV?d00001 From 5c932dc5cc0bdc86bee72957fe20a92bc2dd18b8 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 14 Jan 2020 01:36:37 +0200 Subject: [PATCH 13/20] - fixed an issue in Gerber parser introduced recently due of changes made to make Gerber files produced by Sprint Layout --- README.md | 1 + flatcamParsers/ParseGerber.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 414cffad..6e9d4270 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ CAD program, and create G-Code for Isolation routing. - 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 12.01.2020 diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index 7fbfce1f..16ce8f37 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -835,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: From f9ec233b0f4d27417ec3279be1cd4eecc1ef3e85 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 14 Jan 2020 02:45:03 +0200 Subject: [PATCH 14/20] - working on the Align Objects Tool --- FlatCAMApp.py | 3 + README.md | 1 + flatcamTools/ToolAlignObjects.py | 104 ++++++++++++++++++++++--------- 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index ca691c00..b7aa387b 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -968,6 +968,9 @@ class App(QtCore.QObject): "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', diff --git a/README.md b/README.md index 6e9d4270..ac71ea1e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ CAD program, and create G-Code for Isolation routing. - 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 diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index e787d0c6..a0e031ab 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -8,7 +8,7 @@ from PyQt5 import QtWidgets, QtGui, QtCore from FlatCAMTool import FlatCAMTool -from flatcamGUI.GUIElements import FCComboBox +from flatcamGUI.GUIElements import FCComboBox, RadioSet from copy import deepcopy @@ -61,7 +61,7 @@ class AlignObjects(FlatCAMTool): grid0.addWidget(self.aligned_label, 0, 0, 1, 2) # Type of object to be aligned - self.type_obj_combo = QtWidgets.QComboBox() + self.type_obj_combo = FCComboBox() self.type_obj_combo.addItem("Gerber") self.type_obj_combo.addItem("Excellon") self.type_obj_combo.addItem("Geometry") @@ -81,7 +81,7 @@ class AlignObjects(FlatCAMTool): grid0.addWidget(self.type_obj_combo, 2, 1) # Object to be aligned - self.object_combo = QtWidgets.QComboBox() + 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) @@ -106,7 +106,7 @@ class AlignObjects(FlatCAMTool): grid0.addWidget(self.aligned_label, 6, 0, 1, 2) # Type of object to be aligned to = aligner - self.type_aligner_obj_combo = QtWidgets.QComboBox() + 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.addItem("Geometry") @@ -126,7 +126,7 @@ class AlignObjects(FlatCAMTool): grid0.addWidget(self.type_aligner_obj_combo, 7, 1) # Object to be aligned to = aligner - self.aligner_object_combo = QtWidgets.QComboBox() + 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) @@ -144,6 +144,30 @@ class AlignObjects(FlatCAMTool): separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) grid0.addWidget(separator_line, 9, 0, 1, 2) + # Alignment Type + self.a_type_lbl = QtWidgets.QLabel('%s:' % _("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( @@ -191,14 +215,13 @@ class AlignObjects(FlatCAMTool): self.aligned_obj = None self.aligner_obj = None - # here store the alignment points for the aligned object - self.aligned_clicked_points = list() + # this is one of the objects: self.aligned_obj or self.aligner_obj + self.target_obj = None - # here store the alignment points for the aligner object - self.aligner_clicked_points = list() + # here store the alignment points + self.clicked_points = list() - # counter for the clicks - self.click_cnt = 0 + self.align_type = None def run(self, toggle=True): self.app.report_usage("ToolAlignObjects()") @@ -233,7 +256,12 @@ class AlignObjects(FlatCAMTool): def set_tool_ui(self): self.reset_fields() - self.click_cnt = 0 + self.clicked_points = list() + self.target_obj = None + self.aligned_obj = None + self.aligner_obj = 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() @@ -268,6 +296,8 @@ class AlignObjects(FlatCAMTool): 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 @@ -285,6 +315,7 @@ class AlignObjects(FlatCAMTool): self.local_connected = True self.app.inform.emit(_("Get First alignment point on the aligned object.")) + self.target_obj = self.aligned_obj def on_mouse_click_release(self, event): if self.app.is_legacy is False: @@ -311,7 +342,7 @@ class AlignObjects(FlatCAMTool): for geo in tool_dict['solid_geometry']: if click_pt.within(geo): center_pt = geo.centroid - self.click_points.append( + self.clicked_points.append( [ float('%.*f' % (self.decimals, center_pt.x)), float('%.*f' % (self.decimals, center_pt.y)) @@ -325,7 +356,7 @@ class AlignObjects(FlatCAMTool): if click_pt.within(geo_el['solid']): if isinstance(geo_el['follow'], Point): center_pt = geo_el['solid'].centroid - self.click_points.append( + self.clicked_points.append( [ float('%.*f' % (self.decimals, center_pt.x)), float('%.*f' % (self.decimals, center_pt.y)) @@ -334,24 +365,37 @@ class AlignObjects(FlatCAMTool): self.check_points() elif event.button == right_button and self.app.event_is_dragging is False: - if not len(self.click_points): - self.reset_calibration_points() - self.disconnect_cal_events() - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled by user request.")) + 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.aligned_click_points) == 1: - self.app.inform.emit(_("Get Second alignment point on aligned object. " - "Or right click to get First alignment point on the aligner object.")) + if self.align_type == 'sp': + if len(self.clicked_points) == 1: + self.app.inform.emit(_("Get First alignment point on the aligner object.")) + # TODO: not working + self.target_obj = self.aligner_obj - if len(self.aligned_click_points) == 2: - self.app.inform.emit(_("Get First alignment point on the aligner object.")) + if len(self.clicked_points) == 2: + self.app.inform.emit('[success] %s' % _("Done.")) + self.align_translate() + self.disconnect_cal_events() + else: + if len(self.clicked_points) == 1: + self.app.inform.emit(_("Get Second alignment point on aligned object. Or right click to cancel.")) - if len(self.aligner_click_points) == 1: - self.app.inform.emit(_("Get Second alignment point on the aligner object. Or right click to finish.")) - self.align_translate() - self.align_rotate() - self.disconnect_cal_events() + if len(self.clicked_points) == 2: + self.app.inform.emit(_("Get First alignment point on the aligner object.")) + self.target_obj = self.aligner_obj + + if len(self.clicked_points) == 3: + self.app.inform.emit(_("Get Second alignment point on the aligner object. Or right click to cancel.")) + + if len(self.clicked_points) == 4: + self.app.inform.emit('[success] %s' % _("Done.")) + self.align_translate() + self.align_rotate() + self.disconnect_cal_events() def align_translate(self): pass @@ -416,7 +460,9 @@ class AlignObjects(FlatCAMTool): self.canvas.graph_event_disconnect(self.mr) self.local_connected = False - self.click_cnt = 0 + self.target_obj = None + self.aligned_obj = None + self.aligner_obj = None def reset_fields(self): self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) From 853f3f5d125df08bcf4690a532d04de70b39cc3b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 14 Jan 2020 03:27:15 +0200 Subject: [PATCH 15/20] - working on the Align Objects Tool --- flatcamTools/ToolAlignObjects.py | 35 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index a0e031ab..505c5a30 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -10,11 +10,8 @@ from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCComboBox, RadioSet -from copy import deepcopy - -import numpy as np - from shapely.geometry import Point +from shapely.affinity import translate, rotate import gettext import FlatCAMTranslation as fcTranslate @@ -221,6 +218,9 @@ class AlignObjects(FlatCAMTool): # here store the alignment points self.clicked_points = list() + self.new_start = None + self.new_stop = None + self.align_type = None def run(self, toggle=True): @@ -286,9 +286,9 @@ class AlignObjects(FlatCAMTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no aligned FlatCAM object selected...")) return - aligner_obj_sel_index = self.object_combo.currentIndex() + aligner_obj_sel_index = self.aligner_object_combo.currentIndex() aligner_obj_model_index = self.app.collection.index( - aligner_obj_sel_index, 0, self.object_combo.rootModelIndex()) + aligner_obj_sel_index, 0, self.aligner_object_combo.rootModelIndex()) try: self.aligner_obj = aligner_obj_model_index.internalPointer().obj @@ -373,12 +373,13 @@ class AlignObjects(FlatCAMTool): if self.align_type == 'sp': if len(self.clicked_points) == 1: self.app.inform.emit(_("Get First alignment point on the aligner object.")) - # TODO: not working self.target_obj = self.aligner_obj if len(self.clicked_points) == 2: self.app.inform.emit('[success] %s' % _("Done.")) self.align_translate() + self.app.plot_all() + self.disconnect_cal_events() else: if len(self.clicked_points) == 1: @@ -395,13 +396,29 @@ class AlignObjects(FlatCAMTool): self.app.inform.emit('[success] %s' % _("Done.")) self.align_translate() self.align_rotate() + self.app.plot_all() + self.disconnect_cal_events() def align_translate(self): - pass + dx = self.clicked_points[1][0] - self.clicked_points[0][0] + dy = self.clicked_points[1][1] - self.clicked_points[0][1] + + if self.align_type == 'dp': + self.new_start = translate(Point(self.clicked_points[2]), xoff=dx, yoff=dy) + self.new_dest = translate(Point(self.clicked_points[3]), xoff=dx, yoff=dy) + + 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): - pass + print(self.new_start.x == self.new_dest.x) def execute(self): aligned_name = self.object_combo.currentText() From 9a3f3b600b6aa733df09808ea40ad394ee863b3b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 14 Jan 2020 16:23:23 +0200 Subject: [PATCH 16/20] - 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 --- FlatCAMApp.py | 32 +++-- README.md | 5 + flatcamGUI/PreferencesUI.py | 72 +++++++---- flatcamTools/ToolAlignObjects.py | 106 +++++++++++----- flatcamTools/ToolExtractDrills.py | 194 +++++++++++++++++++++++++++--- 5 files changed, 326 insertions(+), 83 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index b7aa387b..80df6cab 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -957,6 +957,7 @@ class App(QtCore.QObject): # 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, @@ -1598,6 +1599,7 @@ class App(QtCore.QObject): # 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, @@ -4277,9 +4279,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']) @@ -12305,19 +12318,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: diff --git a/README.md b/README.md index ac71ea1e..ca9dc47d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +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 + 13.01.2020 - fixed a small GUI issue in Excellon UI when Basic mode is active diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index a073b534..41ae3f32 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -7697,14 +7697,20 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): grid_lay.addWidget(separator_line, 8, 0, 1, 2) # ## Axis - self.hole_size_radio = RadioSet([{'label': _("Fixed"), 'value': 'fixed'}, - {'label': _("Proportional"), 'value': 'prop'}]) - self.hole_size_label = QtWidgets.QLabel('%s:' % _("Hole Size")) + 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:' % _("Method")) self.hole_size_label.setToolTip( - _("The type of hole size. Can be:\n" - "- Fixed -> all holes will have a set size\n" - "- Proprotional -> each hole will havea a variable size\n" - "such as to preserve a set annular ring")) + _("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) @@ -7716,27 +7722,31 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) grid_lay.addWidget(separator_line, 10, 0, 1, 2) + # Annular Ring + self.fixed_label = QtWidgets.QLabel('%s' % _("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:' % _("Diameter")) + self.dia_label = QtWidgets.QLabel('%s:' % _("value")) self.dia_label.setToolTip( _("Fixed hole diameter.") ) - grid_lay.addWidget(self.dia_label, 11, 0) - grid_lay.addWidget(self.dia_entry, 11, 1) + grid_lay.addWidget(self.dia_label, 12, 0) + grid_lay.addWidget(self.dia_entry, 12, 1) # Annular Ring value - self.ring_label = QtWidgets.QLabel('%s' % _("Annular Ring")) + self.ring_label = QtWidgets.QLabel('%s' % _("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, 12, 0, 1, 2) + grid_lay.addWidget(self.ring_label, 13, 0, 1, 2) # Circular Annular Ring Value self.circular_ring_label = QtWidgets.QLabel('%s:' % _("Circular")) @@ -7748,8 +7758,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): 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, 13, 0) - grid_lay.addWidget(self.circular_ring_entry, 13, 1) + 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")) @@ -7761,8 +7771,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): 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, 14, 0) - grid_lay.addWidget(self.oblong_ring_entry, 14, 1) + 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")) @@ -7774,8 +7784,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): 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, 15, 0) - grid_lay.addWidget(self.square_ring_entry, 15, 1) + 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")) @@ -7787,8 +7797,8 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): 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, 16, 0) - grid_lay.addWidget(self.rectangular_ring_entry, 16, 1) + 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")) @@ -7800,8 +7810,26 @@ class Tools2EDrillsPrefGroupUI(OptionsGroupUI): 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, 17, 0) - grid_lay.addWidget(self.other_ring_entry, 17, 1) + grid_lay.addWidget(self.other_ring_label, 18, 0) + grid_lay.addWidget(self.other_ring_entry, 18, 1) + + self.prop_label = QtWidgets.QLabel('%s' % _("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() diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index 505c5a30..91594c16 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -54,18 +54,16 @@ class AlignObjects(FlatCAMTool): grid0.setColumnStretch(1, 1) self.layout.addLayout(grid0) - self.aligned_label = QtWidgets.QLabel('%s' % _("Selection of the aligned object")) + self.aligned_label = QtWidgets.QLabel('%s' % _("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.addItem("Geometry") 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.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png")) self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type")) self.type_obj_combo_label.setToolTip( @@ -96,9 +94,9 @@ class AlignObjects(FlatCAMTool): separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) grid0.addWidget(separator_line, 4, 0, 1, 2) - self.aligned_label = QtWidgets.QLabel('%s' % _("Selection of the aligner object")) + self.aligned_label = QtWidgets.QLabel('%s' % _("Selection of the TARGET object")) self.aligned_label.setToolTip( - _("Object to which the other objects will be aligned to (moved).") + _("Object to which the other objects will be aligned to (moved to).") ) grid0.addWidget(self.aligned_label, 6, 0, 1, 2) @@ -106,11 +104,9 @@ class AlignObjects(FlatCAMTool): 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.addItem("Geometry") 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.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png")) self.type_aligner_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type")) self.type_aligner_obj_combo_label.setToolTip( @@ -219,10 +215,17 @@ class AlignObjects(FlatCAMTool): self.clicked_points = list() self.new_start = None - self.new_stop = None + self.new_dest = None 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()") @@ -261,6 +264,11 @@ class AlignObjects(FlatCAMTool): 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: @@ -277,6 +285,7 @@ class AlignObjects(FlatCAMTool): 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()) @@ -314,8 +323,14 @@ class AlignObjects(FlatCAMTool): self.local_connected = True - self.app.inform.emit(_("Get First alignment point on the aligned object.")) + 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: @@ -365,40 +380,47 @@ class AlignObjects(FlatCAMTool): 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 self.align_type == 'sp': - if len(self.clicked_points) == 1: - self.app.inform.emit(_("Get First alignment point on the aligner object.")) - self.target_obj = self.aligner_obj + 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 len(self.clicked_points) == 2: + self.align_translate() + if self.align_type == 'sp': self.app.inform.emit('[success] %s' % _("Done.")) - self.align_translate() self.app.plot_all() self.disconnect_cal_events() - else: - if len(self.clicked_points) == 1: - self.app.inform.emit(_("Get Second alignment point on aligned object. Or right click to cancel.")) + 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) == 2: - self.app.inform.emit(_("Get First alignment point on the aligner object.")) - self.target_obj = self.aligner_obj + 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) == 3: - self.app.inform.emit(_("Get Second alignment point on the aligner object. Or right click to cancel.")) + if len(self.clicked_points) == 4: + self.align_rotate() + self.app.inform.emit('[success] %s' % _("Done.")) - if len(self.clicked_points) == 4: - self.app.inform.emit('[success] %s' % _("Done.")) - self.align_translate() - self.align_rotate() - self.app.plot_all() - - self.disconnect_cal_events() + self.disconnect_cal_events() + self.app.plot_all() def align_translate(self): dx = self.clicked_points[1][0] - self.clicked_points[0][0] @@ -477,9 +499,27 @@ class AlignObjects(FlatCAMTool): self.canvas.graph_event_disconnect(self.mr) self.local_connected = False - 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 + + 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())) diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index e0c72c95..d75a61b5 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -125,15 +125,25 @@ class ToolExtractDrills(FlatCAMTool): grid1.setColumnStretch(0, 0) grid1.setColumnStretch(1, 1) + self.method_label = QtWidgets.QLabel('%s' % _("Method")) + grid1.addWidget(self.method_label, 2, 0, 1, 2) + # ## Axis - self.hole_size_radio = RadioSet([{'label': _("Fixed"), 'value': 'fixed'}, - {'label': _("Proportional"), 'value': 'prop'}]) + 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 type of hole size. Can be:\n" - "- Fixed -> all holes will have a set size\n" - "- Proprotional -> each hole will havea a variable size\n" - "such as to preserve a set annular ring")) + _("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) @@ -145,18 +155,27 @@ class ToolExtractDrills(FlatCAMTool): separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) grid1.addWidget(separator_line, 5, 0, 1, 2) + # Annular Ring + self.fixed_label = QtWidgets.QLabel('%s' % _("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:' % _("Diameter")) + self.dia_label = QtWidgets.QLabel('%s:' % _("Value")) self.dia_label.setToolTip( _("Fixed hole diameter.") ) - grid1.addWidget(self.dia_label, 7, 0) - grid1.addWidget(self.dia_entry, 7, 1) + 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) @@ -173,7 +192,7 @@ class ToolExtractDrills(FlatCAMTool): self.ring_box.addLayout(grid2) # Annular Ring value - self.ring_label = QtWidgets.QLabel('%s' % _("Annular Ring")) + self.ring_label = QtWidgets.QLabel('%s' % _("Fixed Annular Ring")) self.ring_label.setToolTip( _("The size of annular ring.\n" "The copper sliver between the drill hole exterior\n" @@ -246,6 +265,35 @@ class ToolExtractDrills(FlatCAMTool): 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('%s' % _("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( @@ -280,6 +328,13 @@ class ToolExtractDrills(FlatCAMTool): 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) @@ -359,6 +414,8 @@ class ToolExtractDrills(FlatCAMTool): 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() @@ -368,6 +425,8 @@ class ToolExtractDrills(FlatCAMTool): 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() @@ -376,7 +435,7 @@ class ToolExtractDrills(FlatCAMTool): try: fcobj = model_index.internalPointer().obj - except Exception as e: + except Exception: self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) return @@ -421,7 +480,7 @@ class ToolExtractDrills(FlatCAMTool): 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 - else: + elif mode == 'ring': drills_found = set() for apid, apid_value in fcobj.apertures.items(): ap_type = apid_value['type'] @@ -435,9 +494,9 @@ class ToolExtractDrills(FlatCAMTool): height = float(apid_value['height']) if self.oblong_cb.get_value(): if width > height: - dia = float(apid_value['height']) - (2 * rect_r_val) + dia = float(apid_value['height']) - (2 * oblong_r_val) else: - dia = float(apid_value['width']) - (2 * rect_r_val) + dia = float(apid_value['width']) - (2 * oblong_r_val) elif ap_type == 'R': width = float(apid_value['width']) height = float(apid_value['height']) @@ -506,6 +565,91 @@ class ToolExtractDrills(FlatCAMTool): 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 @@ -518,16 +662,36 @@ class ToolExtractDrills(FlatCAMTool): 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) - else: + + 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) From a8bea7805ed59e6044763fc8d2d8f83647794990 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 14 Jan 2020 17:18:24 +0200 Subject: [PATCH 17/20] - working on the Dual Point option in Align Objects Tool - angle has to be recalculated --- README.md | 1 + flatcamTools/ToolAlignObjects.py | 34 ++++++++++++++++++++++-------- flatcamTools/ToolNonCopperClear.py | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ca9dc47d..79b484b3 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ CAD program, and create G-Code for Isolation routing. - 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 13.01.2020 diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index 91594c16..9d79de17 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -10,8 +10,10 @@ from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCComboBox, RadioSet +import math + from shapely.geometry import Point -from shapely.affinity import translate, rotate +from shapely.affinity import translate import gettext import FlatCAMTranslation as fcTranslate @@ -68,7 +70,7 @@ class AlignObjects(FlatCAMTool): 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, Excellon or Geometry.\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.") ) @@ -111,7 +113,7 @@ class AlignObjects(FlatCAMTool): 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, Excellon or Geometry.\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.") ) @@ -394,8 +396,8 @@ class AlignObjects(FlatCAMTool): self.set_color() if len(self.clicked_points) == 2: - self.align_translate() if self.align_type == 'sp': + self.align_translate() self.app.inform.emit('[success] %s' % _("Done.")) self.app.plot_all() @@ -416,6 +418,7 @@ class AlignObjects(FlatCAMTool): self.set_color() if len(self.clicked_points) == 4: + self.align_translate() self.align_rotate() self.app.inform.emit('[success] %s' % _("Done.")) @@ -426,10 +429,6 @@ class AlignObjects(FlatCAMTool): dx = self.clicked_points[1][0] - self.clicked_points[0][0] dy = self.clicked_points[1][1] - self.clicked_points[0][1] - if self.align_type == 'dp': - self.new_start = translate(Point(self.clicked_points[2]), xoff=dx, yoff=dy) - self.new_dest = translate(Point(self.clicked_points[3]), xoff=dx, yoff=dy) - self.aligned_obj.offset((dx, dy)) # Update the object bounding box options @@ -440,7 +439,24 @@ class AlignObjects(FlatCAMTool): self.aligned_obj.options['ymax'] = d def align_rotate(self): - print(self.new_start.x == self.new_dest.x) + dx = self.clicked_points[1][0] - self.clicked_points[0][0] + dy = self.clicked_points[1][1] - self.clicked_points[0][1] + + new_start_pt = translate(Point(self.clicked_points[2]), xoff=dx, yoff=dy) + self.new_start = (new_start_pt.x, new_start_pt.y) + self.new_dest = self.clicked_points[3] + + origin_pt = self.clicked_points[1] + + sec_dx = self.new_dest[0] - self.new_start[0] + sec_dy = self.new_dest[1] - self.new_start[1] + + rotation_not_needed = (abs(self.new_start[0] - self.new_dest[0]) <= (10 ** -self.decimals)) or \ + (abs(self.new_start[1] - self.new_dest[1]) <= (10 ** -self.decimals)) + if rotation_not_needed is False: + # calculate rotation angle + angle = math.degrees(math.atan(sec_dy / sec_dx)) + self.aligned_obj.rotate(angle=angle, point=origin_pt) def execute(self): aligned_name = self.object_combo.currentText() diff --git a/flatcamTools/ToolNonCopperClear.py b/flatcamTools/ToolNonCopperClear.py index 9a6e047e..8b6ba546 100644 --- a/flatcamTools/ToolNonCopperClear.py +++ b/flatcamTools/ToolNonCopperClear.py @@ -641,7 +641,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) From acfb1ca9e7f1febbefb41668ef76b2122c4d2091 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 15 Jan 2020 00:55:12 +0200 Subject: [PATCH 18/20] - finished Dual Point option in Align Objects Tool --- README.md | 1 + flatcamTools/ToolAlignObjects.py | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 79b484b3..5e49cd87 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ CAD program, and create G-Code for Isolation routing. - 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 diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index 9d79de17..7368b865 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -216,9 +216,6 @@ class AlignObjects(FlatCAMTool): # here store the alignment points self.clicked_points = list() - self.new_start = None - self.new_dest = None - self.align_type = None # old colors of objects involved in the alignment @@ -442,20 +439,25 @@ class AlignObjects(FlatCAMTool): dx = self.clicked_points[1][0] - self.clicked_points[0][0] dy = self.clicked_points[1][1] - self.clicked_points[0][1] - new_start_pt = translate(Point(self.clicked_points[2]), xoff=dx, yoff=dy) - self.new_start = (new_start_pt.x, new_start_pt.y) - self.new_dest = self.clicked_points[3] + 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] - sec_dx = self.new_dest[0] - self.new_start[0] - sec_dy = self.new_dest[1] - self.new_start[1] + dxd = new_dest[0] - origin_pt[0] + dyd = new_dest[1] - origin_pt[1] - rotation_not_needed = (abs(self.new_start[0] - self.new_dest[0]) <= (10 ** -self.decimals)) or \ - (abs(self.new_start[1] - self.new_dest[1]) <= (10 ** -self.decimals)) + 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 = math.degrees(math.atan(sec_dy / sec_dx)) + 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 execute(self): From 821014f719e681dd26ddaa77687eae1c8c6019c9 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 15 Jan 2020 02:50:27 +0200 Subject: [PATCH 19/20] - 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 --- FlatCAMApp.py | 155 ++++++++++++++++++++++++++++++- README.md | 5 + flatcamGUI/FlatCAMGUI.py | 63 +++++++++++-- flatcamTools/ToolAlignObjects.py | 45 --------- share/extract_drill16.png | Bin 0 -> 459 bytes share/extract_drill32.png | Bin 0 -> 745 bytes share/locate16.png | Bin 0 -> 565 bytes share/locate32.png | Bin 0 -> 900 bytes 8 files changed, 214 insertions(+), 54 deletions(-) create mode 100644 share/extract_drill16.png create mode 100644 share/extract_drill32.png create mode 100644 share/locate16.png create mode 100644 share/locate32.png diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 80df6cab..5324290e 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -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, @@ -1956,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) @@ -3241,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) @@ -3250,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)) @@ -7288,7 +7297,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("    X: %.4f   " + "Y: %.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("Dx: %.4f   Dy: " + "%.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) diff --git a/README.md b/README.md index 5e49cd87..2c446e8a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing. ================================================= +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 diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 111a9895..86974e3f 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -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): SHIFT+G  %s + + SHIFT+J +  %s + SHIFT+M  %s @@ -1506,6 +1519,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):     + + ALT+A +  %s + ALT+C  %s @@ -1518,6 +1535,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ALT+E  %s + + ALT+I +  %s + ALT+J  %s @@ -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) diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index 7368b865..d1e77442 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -224,7 +224,6 @@ class AlignObjects(FlatCAMTool): self.aligned_old_fill_color = None self.aligned_old_line_color = None - def run(self, toggle=True): self.app.report_usage("ToolAlignObjects()") @@ -460,50 +459,6 @@ class AlignObjects(FlatCAMTool): angle = angle_dest - angle_start self.aligned_obj.rotate(angle=angle, point=origin_pt) - def execute(self): - aligned_name = self.object_combo.currentText() - - # Get source object. - try: - aligned_obj = self.app.collection.get_by_name(str(aligned_name)) - except Exception as e: - log.debug("AlignObjects.on_align() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), aligned_name)) - return "Could not retrieve object: %s" % aligned_name - - if aligned_obj is None: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), aligned_obj)) - return "Object not found: %s" % aligned_obj - - aligner_name = self.box_combo.currentText() - - try: - aligner_obj = self.app.collection.get_by_name(aligner_name) - except Exception as e: - log.debug("AlignObjects.on_align() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), aligner_name)) - return "Could not retrieve object: %s" % aligner_name - - if aligner_obj is None: - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), aligner_name)) - - def align_job(): - pass - - proc = self.app.proc_container.new(_("Working...")) - - def job_thread(app_obj): - try: - align_job() - app_obj.inform.emit('[success] %s' % _("Panel created successfully.")) - except Exception as ee: - proc.done() - log.debug(str(ee)) - return - proc.done() - - self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - def disconnect_cal_events(self): # restore the Grid snapping if it was active before if self.grid_status_memory is True: diff --git a/share/extract_drill16.png b/share/extract_drill16.png new file mode 100644 index 0000000000000000000000000000000000000000..4b3a29abcbd40df29de8bdf4ab9798ff03c17576 GIT binary patch literal 459 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJV{wqX6XVU3I`u#fXMsm#F#`je z9|$vEn-I1eC^$dBC&U#<4`#4^vYiXieWoQre!&d-Fcpb6ALp-t2BX literal 0 HcmV?d00001 diff --git a/share/extract_drill32.png b/share/extract_drill32.png new file mode 100644 index 0000000000000000000000000000000000000000..41f740f249b02779fe8d74b1ecfa69ba7e1ea76e GIT binary patch literal 745 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jPK-BC>eK@{oCO|{#S9E= zejv>?paQ`FpAc7|h-TdC#@_ zj!4m{1f7>3Y`Mc;YdY)~4{B#&V&gCFwmJul11?V&$B>FS$q5R421bGb2hKPxezqJt87lRy2f%1PJVA5)cp;pYE{LG0015rna)S zdb5)L`UNYN^yn;D)O92<`1*w_j~)w)UA@-Wy|ATuo!sg^rTP0B7fv*cIdeqhDred= zv1wsE6^&bYXEm&q)snwD+u=Y)|Ecl~stXvHEsTmDPAw05P=0Lfp`%knIVGg?)~$PY zL$sozs`B&4pFcKjC@}u4|0cE$FL>C4e^W1y8Q*L2^CSQ40 z$*IX6OSIMppKQ2$BW00V#dg+jcYkh>&A;^P^ox0wk>P12Ql$x@)_2V|#6?$JToSW4 z;iQ7F*c#=9*VAfadKS%eIM}S5t@TUx-%j5syTqH_$Df_Qs4iXcFL{gL`*ZslCu}#p z7QiOA6c|FPC9V-ADTyViR>?)FK#IZ0z{o(?z);uFG{nHb%D}?P#84Z=Fc8n)>WZQv zH$NpatrE9}G&A0xKn;>08-nxGO3D+9QW?t2%k?tzvWt@w3sUv+i_&MmvylQSV(@hJ Kb6Mw<&;$TR7|D+S literal 0 HcmV?d00001 diff --git a/share/locate32.png b/share/locate32.png new file mode 100644 index 0000000000000000000000000000000000000000..66d630b07edd92fcc124cfdfaaef755ab35847f1 GIT binary patch literal 900 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-GzZ+Rj;xUkjGiz z5n0T@z;^_M8K-LVNdpDhOFVsD*`F}-GjPZ!UiQ@oY6|jnaSZV|zVyod?!HQi;~%Gg z+;Fz%U{sjonF*J=d=*bCbe>RAa`)>np7VhJLe6b9%L($C;YCVr773GtU4uR;tFy76 z>iKHWdh_e=@W;E^zyG~+{%=LzGfBJeC;#qU_x|g@xcjen{rWWb)pybR5ubY|e!e1V z{+sLH%|ctJPj?O0>OZvKCCq;B`n$yn6{&7d8*G~%Y&w~+V?XOb-O#D6*DEwL!e?)i z6=M0U&3s0XCEF`!@fVJ5)AP@HW$Ek)OR9dND5b?5;(U_z#d5QcU$`pR!lvAK_Dl2Q zmsOfg54O(iOx3i>>>QvQK$i zU`F!d-}4#k{6A@Ctp7ga!KA|q>-R-8A4@XYcx&!Po2~!O|Fg(xuTEP%slQ4n^+EvK z0$2a6ukJM`x8J&BdViM_+eE)xa{|B4UHN>;md4FGA7-Ch^sp^evf!X|{-^2BXk24^fuoVMch$)B&Kn;yph-gIw;-K%;r=BF)*+)u&^>V)CMsOWY_J9LD7(#pOTqYiCaU? zXO;&*4U!-mg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kpe1W@O1TaS?83{1OVCZ Bf)W4# literal 0 HcmV?d00001 From 9a9b6908bc8a73d632e183f0716b0b0069f02d94 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 15 Jan 2020 03:02:45 +0200 Subject: [PATCH 20/20] - minor changes --- FlatCAMApp.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 5324290e..dce2111f 100644 --- a/FlatCAMApp.py +++ b/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' @@ -7202,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